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 + + + + \ 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(context, listen: false).user; + final settingsProvider = Provider.of(context, listen: false); + await settingsProvider.changePreferences( + user, aboutMeController.text, isSwitched, isSwitched2); + error = false; + message = "Changed Successfully."; + } catch (e) { + setState(() { + error = true; + message = "Something went wrong!"; + }); + } + } + + @override + Widget build(BuildContext context) { + final User? user = Provider.of(context).user; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + width: Responsive.getGenericPageWidth(context), + child: Column( + children: [ + const SizedBox(height: 10), + const Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SelectableText('About', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), + ], + ), + const SizedBox(height: 10), + AboutMeEdit(aboutMeController), + const Divider(height: 40.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SelectableText( + "Send Email Notifications", + style: TextStyles.bodyBold, + ), + Switch( + value: isSwitched, + activeColor: AppColors.primaryColor, + onChanged: (value) { + setState(() { + isSwitched = value; // Update the state when the switch is toggled + }); + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SelectableText( + "Show Activity in Profile Page", + style: TextStyles.bodyBold, + ), + Switch( + value: isSwitched2, + activeColor: AppColors.primaryColor, + onChanged: (value) { + setState(() { + isSwitched2 = value; // Update the state when the switch is toggled + }); + }, + ), + ], + ), + const SizedBox(height: 20.0), + Container( + width: 400, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => changePreff(), + child: Container( + height: 40.0, + width: MediaQuery.of(context).size.width - 40, + decoration: BoxDecoration( + color: AppColors.primaryColor, borderRadius: BorderRadius.circular(5.0)), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Save', + style: TextStyle( + color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16.0)), + ], + ), + ), + ), + ), + ), + const SizedBox(height: 10), + Text(message), + const Divider(height: 40.0), + Container( + width: 400, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const SizedBox( + width: 500, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Change Password', style: TextStyle(fontSize: 20.0)), + ], + ), + ), + backgroundColor: Colors.white, + shadowColor: Colors.white, + content: ChangePasswordForm(user: user), + ), + ); + }, + child: Container( + height: 40.0, + width: MediaQuery.of(context).size.width - 40, + decoration: BoxDecoration( + color: AppColors.primaryColor, borderRadius: BorderRadius.circular(5.0)), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Change Password', + style: TextStyle( + color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16.0)), + ], + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/change_password_form.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/change_password_form.dart new file mode 100644 index 00000000..c7eccec6 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/change_password_form.dart @@ -0,0 +1,128 @@ +import 'package:collaborative_science_platform/models/user.dart'; +import 'package:collaborative_science_platform/providers/settings_provider.dart'; +import 'package:collaborative_science_platform/utils/colors.dart'; +import 'package:collaborative_science_platform/widgets/app_button.dart'; +import 'package:flutter/material.dart'; +import 'package:collaborative_science_platform/models/profile_data.dart'; +import 'package:collaborative_science_platform/screens/profile_page/widgets/settings_input_widget.dart'; +import 'package:collaborative_science_platform/utils/responsive/responsive.dart'; +import 'package:provider/provider.dart'; + +class ChangePasswordForm extends StatefulWidget { + final User? user; + const ChangePasswordForm({ + super.key, + required this.user, + }); + + @override + State createState() => _ChangePasswordFormState(); +} + +class _ChangePasswordFormState extends State { + ProfileData profileData = ProfileData(); + final oldPassController = TextEditingController(); + final newPassController = TextEditingController(); + + final passwordFocusNode = FocusNode(); + final newPassFocusNode = FocusNode(); + final double x = 300; + +bool buttonState = false; + String errorMessage = ""; + bool error = false; + + @override + void dispose() { + oldPassController.dispose(); + passwordFocusNode.dispose(); + newPassController.dispose(); + newPassFocusNode.dispose(); + super.dispose(); + } + + void changePass() async { + try { + final settingsProvider = Provider.of(context, listen: false); + final int response = await settingsProvider.changePassword( + widget.user, oldPassController.text, newPassController.text); + + if (response == 200) { + setState(() { + error = false; + errorMessage = "Password Changed Successfully."; + }); + } else if (response == 400) { + setState(() { + error = true; + errorMessage = "Current password do not match with given password."; + }); + } + } catch (e) { + setState(() { + error = true; + errorMessage = "Something went wrong!"; + }); + } + } + + void controllerCheck() { + if (newPassController.text.isNotEmpty && + oldPassController.text.isNotEmpty) { + setState(() { + buttonState = true; + }); + } else { + setState(() { + buttonState = false; + }); + } + } + + @override + Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 36.0), + width: Responsive.getGenericPageWidth(context), + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 20.0), + SettingsWidget( + controller: oldPassController, + focusNode: passwordFocusNode, + textType: "Current password", + prefixIcon: Icons.lock, + widgetWidth: screenWidth, + controllerCheck: controllerCheck, + ), + const SizedBox(height: 20.0), + SettingsWidget( + controller: newPassController, + focusNode: newPassFocusNode, + textType: "New password", + prefixIcon: Icons.lock, + widgetWidth: screenWidth, + controllerCheck: controllerCheck, + ), + ], + ), + const SizedBox(height: 20.0), + + AppButton( + onTap: () => oldPassController.text.isNotEmpty && newPassController.text.isNotEmpty ? changePass() : {}, + text: "Save", + height: 50, + isActive: buttonState, + // isLoading: isLoading, + ), + const SizedBox(height: 10.0), + Text(errorMessage, style: const TextStyle(fontSize: 16.0),), + ], + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/desktop_edit_profile_button.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/desktop_edit_profile_button.dart new file mode 100644 index 00000000..117da658 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/desktop_edit_profile_button.dart @@ -0,0 +1,55 @@ +import 'package:collaborative_science_platform/screens/profile_page/widgets/account_settings_form.dart'; +import 'package:flutter/material.dart'; +import 'package:collaborative_science_platform/utils/colors.dart'; + +class DesktopEditProfileButton extends StatelessWidget { + const DesktopEditProfileButton({super.key}); + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + // Show Popup with EditProfileForm content + showDialog( + context: context, + builder: (context) => const AlertDialog( + title: SizedBox( + width: 500, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Edit Profile', style: TextStyle(fontSize: 20.0)), + ], + ), + ), + backgroundColor: Colors.white, + shadowColor: Colors.white, + content: AccountSettingsForm(), + ), + ); + }, + child: Container( + height: 40.0, + width: MediaQuery.of(context).size.width / 3, + decoration: BoxDecoration( + color: AppColors.primaryColor, + borderRadius: BorderRadius.circular(10.0)), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.edit, color: Colors.white), + SizedBox(width: 10.0), + Text('Edit Profile', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16.0)), + ], + ), + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/logout_button.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/logout_button.dart new file mode 100644 index 00000000..0be584f0 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/logout_button.dart @@ -0,0 +1,33 @@ +import 'package:collaborative_science_platform/providers/auth.dart'; +import 'package:collaborative_science_platform/screens/auth_screens/please_login_page.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:go_router/go_router.dart'; + +class LogOutButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + Provider.of(context, listen: false).logout(); + context.go(PleaseLoginPage.routeName); + }, + child: Container( + height: 40.0, + // width: MediaQuery.of(context).size.width - 80, + decoration: BoxDecoration(color: Colors.grey, borderRadius: BorderRadius.circular(5.0)), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Logout', + style: + TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16.0)), + ], + ), + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/mobile_edit_profile_button.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/mobile_edit_profile_button.dart new file mode 100644 index 00000000..225f216f --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/mobile_edit_profile_button.dart @@ -0,0 +1,37 @@ +import 'package:collaborative_science_platform/screens/profile_page/account_settings_page.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class MobileEditProfileButton extends StatelessWidget { + const MobileEditProfileButton({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(right: 10.0), + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + context.go(AccountSettingsPage.routeName); + }, + child: Container( + height: 40.0, + // width: MediaQuery.of(context).size.width - 80, + decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(5.0)), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.edit, color: Colors.white), + SizedBox(width: 10.0), + Text('Edit Profile', + style: TextStyle( + color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16.0)), + ], + ), + ), + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_activity_tabbar.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_activity_tabbar.dart new file mode 100644 index 00000000..fee7c83d --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_activity_tabbar.dart @@ -0,0 +1,61 @@ +import 'package:collaborative_science_platform/screens/profile_page/widgets/profile_nav_bar_item.dart'; +import 'package:collaborative_science_platform/utils/text_styles.dart'; +import 'package:collaborative_science_platform/widgets/card_container.dart'; +import 'package:flutter/material.dart'; + +class ProfileActivityTabBar extends StatefulWidget { + final Function callback; + const ProfileActivityTabBar({ + super.key, + required this.callback, + }); + @override + State createState() => _ProfileActivityTabBar(); +} + +class _ProfileActivityTabBar extends State { + int currentIndex = 0; + + void updateIndex(int newIndex) { + setState(() { + currentIndex = newIndex; + }); + widget.callback(newIndex); + } + + @override + Widget build(BuildContext context) { + return CardContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Text( + "Activities", + style: TextStyles.bodyBlack, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + NavigationBarItem( + callback: updateIndex, + icon: Icons.content_copy, + index: 0, + text: "Published Nodes", + isSelected: currentIndex == 0, + ), + NavigationBarItem( + callback: updateIndex, + icon: Icons.question_answer, + index: 1, + isSelected: currentIndex == 1, + text: "Q/A", + ), + ], + ), + ], + )); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_nav_bar_item.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_nav_bar_item.dart new file mode 100644 index 00000000..948e901e --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_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/profile_page/widgets/profile_node_card.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_node_card.dart new file mode 100644 index 00000000..81809677 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_node_card.dart @@ -0,0 +1,71 @@ +import 'package:collaborative_science_platform/models/profile_data.dart'; +import 'package:flutter/material.dart'; + +class ProfileNodeCard extends StatelessWidget { + final Node profileNode; + final Color? color; + final Function() onTap; + + const ProfileNodeCard({ + super.key, + required this.profileNode, + this.color, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return 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.start, + children: [ + SelectableText( + profileNode.nodeTitle, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18.0, + ), + ), + const SizedBox(height: 8.0), + SelectableText( + profileNode.contributors + .map((user) => "${user.firstName} ${user.lastName} (${user.email})") + .join(", "), + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14.0, + color: Colors.grey, + ), + ), + const SizedBox(height: 8.0), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + profileNode.publishDate, + style: const TextStyle( + color: Colors.grey, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/question_activity.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/question_activity.dart new file mode 100644 index 00000000..84e537e0 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/question_activity.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +// TODO: currently no API to get questions +class QuestionActivity extends StatelessWidget { + const QuestionActivity({super.key}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(0), + scrollDirection: Axis.vertical, + shrinkWrap: true, + itemCount: 10, + itemBuilder: (BuildContext context, int index) { + return Card( + elevation: 4.0, + margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + child: InkWell( + // onTap: Navigate to the screen of the question/answer + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText( + "question/answer $index", + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18.0, + ), + ), + const SizedBox(height: 8.0), + const SizedBox(height: 8.0), + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SelectableText( + 'some date', + style: TextStyle( + color: Colors.grey, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/settings_input_widget.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/settings_input_widget.dart new file mode 100644 index 00000000..bc3e2824 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/settings_input_widget.dart @@ -0,0 +1,101 @@ +import 'package:collaborative_science_platform/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class SettingsWidget extends StatelessWidget { + final TextEditingController controller; + final FocusNode focusNode; + final String textType; + final IconData prefixIcon; + final double widgetWidth; + final Function controllerCheck; + + const SettingsWidget({ + super.key, + required this.controller, + required this.focusNode, + required this.textType, + required this.prefixIcon, + required this.widgetWidth, + required this.controllerCheck, + }); + + @override + Widget build(BuildContext context) { + return Card( + // Wrap the container with a Card + elevation: 2, // Add elevation for a shadow effect + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Container( + width: widgetWidth, + padding: const EdgeInsets.symmetric(horizontal: 4), + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + Icon(prefixIcon, color: Colors.grey), + const SizedBox(height: 5.0), + ], + ), + const SizedBox(width: 10.0), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 2), + Text( + textType, + style: const TextStyle( + color: AppColors.primaryColor, + fontSize: 14.0, + ), + ), + TextField( + controller: controller, + focusNode: focusNode, + onChanged: (_) {controllerCheck();}, + keyboardType: TextInputType.text, + cursorColor: AppColors.primaryColor, + maxLines: null, // Allow the input to have multiple lines + style: const TextStyle(color: Colors.black), + decoration: InputDecoration( + enabledBorder: const UnderlineInputBorder( + borderSide: BorderSide( + color: AppColors.primaryColor, + ), // Color of the line + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + borderSide: const BorderSide( + color: AppColors.primaryColor, + ), + ), + focusedBorder: const UnderlineInputBorder( + borderSide: BorderSide( + color: AppColors.secondaryDarkColor, + ), // Color of the line when focused + ), + isCollapsed: + true, // This makes the input and hint text closer + contentPadding: const EdgeInsets.symmetric(vertical: 4), + fillColor: Colors.white, + filled: true, + hintMaxLines: null, + hintStyle: + const TextStyle(color: Colors.grey, fontSize: 18.0), + ), + ), + const SizedBox(height: 10.0), + ], + ), + ), + const Padding(padding: EdgeInsets.all(10.0)), + ], + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/create_workspace_page/mobile_create_workspace_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/create_workspace_page/mobile_create_workspace_page.dart new file mode 100644 index 00000000..41c56423 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/create_workspace_page/mobile_create_workspace_page.dart @@ -0,0 +1,25 @@ +import 'package:collaborative_science_platform/screens/page_with_appbar/page_with_appbar.dart'; +import 'package:collaborative_science_platform/widgets/simple_app_bar.dart'; +import 'package:flutter/material.dart'; + +class MobileCreateWorkspacePage extends StatefulWidget { + static const routeName = '/create-workspace'; + const MobileCreateWorkspacePage({super.key}); + + @override + State createState() => _MobileCreateWorkspacePageState(); +} + +class _MobileCreateWorkspacePageState extends State { + @override + Widget build(BuildContext context) { + return const PageWithAppBar( + appBar: SimpleAppBar(title: "Create Workspace"), + child: Center( + child: Text( + "This is the mobile page where you can create workspace", + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/mobile_workspace_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/mobile_workspace_page.dart new file mode 100644 index 00000000..26c6d0a8 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/mobile_workspace_page.dart @@ -0,0 +1,319 @@ +import 'package:carousel_slider/carousel_slider.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/models/workspaces_page/workspaces_object.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/app_alert_dialog.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/mobile_workspace_page/widget/mobile_workspace_content.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/create_workspace_form.dart'; +import 'package:collaborative_science_platform/utils/responsive/responsive.dart'; +import 'package:flutter/material.dart'; +import '../../../widgets/app_button.dart'; +import '../../home_page/widgets/home_page_appbar.dart'; + +class MobileWorkspacePage extends StatefulWidget { + final Workspace? workspace; + final Workspaces? workspaces; + const MobileWorkspacePage({super.key, required this.workspace, required this.workspaces}); + + @override + State createState() => _MobileWorkspacesPageState(); +} + +class _MobileWorkspacesPageState extends State { + final CarouselController controller = CarouselController(); + + Workspaces workspacesData = Workspaces( + workspaces: [], + pendingWorkspaces: [], + ); + + bool isLoading = false; + bool error = false; + String errorMessage = ""; + + int yourWorkLength = 0; + int pendingLength = 0; + int totalLength = 0; + int current = 1; + int workspaceIndex = 0; + + @override + void initState() { + super.initState(); + getWorkspacesData(); + } + + void getWorkspacesData() { + setState(() { + isLoading = true; + }); + for (int i = 0; i < 4; i++) { + workspacesData.workspaces.add( + WorkspacesObject( + workspaceId: i+1, + workspaceTitle: "Workspace Title xxxxxxxxxxxxxxxxxx ${i+1}", + pending: false, + ), + ); + } + for (int i = 0; i < 2; i++) { + workspacesData.pendingWorkspaces.add( + WorkspacesObject( + workspaceId: i+workspacesData.workspaces.length+1, + workspaceTitle: "Pending Workspace Title ${i+1}", + pending: true, + ), + ); + } + yourWorkLength = workspacesData.workspaces.length; + pendingLength = workspacesData.pendingWorkspaces.length; + totalLength = yourWorkLength + pendingLength; + setState(() { + isLoading = false; + }); + } + + Widget mobileAddNewWorkspaceIcon() { + return CircleAvatar( + radius: 24.0, + backgroundColor: Colors.grey.shade300, + child: IconButton( + iconSize: 28.0, + icon: const Icon(Icons.add), + onPressed: () { + showDialog( + context: context, + builder: (context) => AppAlertDialog( + text: "Create Workspace", + content: const CreateWorkspaceForm(), + actions: [ + AppButton( + text: "Create New Workspace", + height: 50, + onTap: () { + // Create Workspace + Navigator.of(context).pop(); + }, + ), + ], + ), + ); + }, + ), + ); + } + + Widget mobileWorkspaceCard(WorkspacesObject workspacesObject, bool pending) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), + child: SizedBox( + height: 80.0, + child: Card( + elevation: 4.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: InkWell( + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + onTap: pending ? () { // accept or reject the review + showDialog( + context: context, + builder: (context) => AppAlertDialog( + text: "Do you accept the work?", + actions: [ + AppButton( + text: "Accept", + height: 40, + onTap: () { + /* Send to review */ + Navigator.of(context).pop(); + }, + ), + AppButton( + text: "Reject", + height: 40, + onTap: () { Navigator.of(context).pop(); }, + ), + ], + ), + ); + } : () { // send to review + showDialog( + context: context, + builder: (context) => AppAlertDialog( + text: "Do you want to send it to review?", + actions: [ + AppButton( + text: "Yes", + height: 40, + onTap: () { + /* Send to review */ + Navigator.of(context).pop(); + }, + ), + AppButton( + text: "No", + height: 40, + onTap: () { Navigator.of(context).pop(); }, + ), + ], + ), + ); + }, + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(width: 2.0), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + pending ? "Pending" : "Your Work", + style: TextStyle( + color: pending ? Colors.red.shade800 + : Colors.green.shade800, + fontWeight: FontWeight.w500, + fontSize: 15.0, + ), + ), + Text( + workspacesObject.workspaceTitle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 18.0, + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 3.0), + child: pending ? const Icon(Icons.keyboard_arrow_right) + : const Icon(Icons.send), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + Widget slidingWorkspaceList() { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CarouselSlider( + carouselController: controller, + items: List.generate( + totalLength+1, + (index) => (index == 0) ? mobileAddNewWorkspaceIcon() + : (index <= yourWorkLength) ? mobileWorkspaceCard( + workspacesData.workspaces[index-1], + false, + ) : mobileWorkspaceCard( + workspacesData.pendingWorkspaces[index-yourWorkLength-1], + true, + ), + ), + options: CarouselOptions( + scrollPhysics: const ScrollPhysics(), + height: 100, + autoPlay: false, + viewportFraction: 0.8, + enableInfiniteScroll: false, + initialPage: current, + enlargeCenterPage: true, + enlargeStrategy: CenterPageEnlargeStrategy.zoom, + enlargeFactor: 0.3, + onPageChanged: (index, reason) { + // I added this conditional to reduce the number + // of build operation for the workspace. + // Going to slide 1 from slide 2 or vice versa does not affect the + // workspace content below. So it shouldn't be reloaded again. + // However, it doesn't work. One that solves this problem wins a chukulat. + if (index != 0 && current != 0) { + setState(() { + workspaceIndex = index-1; + }); + } + setState(() { + current = index; + }); + }, + ), + ), + Text( + "${current+1}/${totalLength+1}", + style: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + if (isLoading || error) { + return PageWithAppBar( + appBar: const HomePageAppBar(), + child: Center( + child: isLoading + ? const CircularProgressIndicator() + : error + ? SelectableText(errorMessage) + : const SelectableText("Something went wrong!"), + ), + ); + } else { + return PageWithAppBar( + appBar: const HomePageAppBar(), + child: SizedBox( + width: Responsive.getGenericPageWidth(context), + child: ListView( + physics: const ScrollPhysics(), + padding: const EdgeInsets.only(top: 10.0), + children: [ + slidingWorkspaceList(), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Divider(), + ), + (totalLength != 0) ? MobileWorkspaceContent( + workspaceId: (workspaceIndex < yourWorkLength) ? workspacesData.workspaces[workspaceIndex].workspaceId + : workspacesData.pendingWorkspaces[workspaceIndex-yourWorkLength].workspaceId, + pending: (workspaceIndex < yourWorkLength) ? false : true, + ) : const Padding( + padding: EdgeInsets.fromLTRB(16.0, 120.0, 16.0, 0.0), + child: Text( + "You haven't created any workspace yet!", + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 24.0, + ), + ), + ), + ], + ), + ), + ); + } + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/app_alert_dialog.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/app_alert_dialog.dart new file mode 100644 index 00000000..e582afb7 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/app_alert_dialog.dart @@ -0,0 +1,38 @@ + +import 'package:flutter/material.dart'; + +class AppAlertDialog extends StatelessWidget { + final String text; + final Widget? content; + final List? actions; + + const AppAlertDialog({ + super.key, + required this.text, + this.content, + this.actions, + }); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: SizedBox( + width: 500, + child: Text( + text, + maxLines: 3, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.w400 + ), + ), + ), + backgroundColor: Colors.white, + shadowColor: Colors.white, + surfaceTintColor: Colors.white, + content: content, + actions: actions + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/contributor_card.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/contributor_card.dart new file mode 100644 index 00000000..3de14739 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/contributor_card.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import '../../../../models/user.dart'; +import '../../../profile_page/profile_page.dart'; + + +class ContributorCard extends StatelessWidget { + final User contributor; + + const ContributorCard({ + super.key, + required this.contributor, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), + child: SizedBox( + height: 90.0, + child: Card( + elevation: 4.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: InkWell( + onTap: () { + // Navigate to the contributor's profile page + final String encodedEmail = Uri.encodeComponent(contributor.email); + context.push('${ProfilePage.routeName}/$encodedEmail'); + }, + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "${contributor.firstName} ${contributor.lastName}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 18.0, + ), + ), + const SizedBox(height: 2.0), + Text( + contributor.email, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16.0, + color: Colors.grey, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/entry_card.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/entry_card.dart new file mode 100644 index 00000000..2de702c4 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/entry_card.dart @@ -0,0 +1,285 @@ +import 'package:collaborative_science_platform/screens/workspace_page/mobile_workspace_page/widget/app_alert_dialog.dart'; +import 'package:collaborative_science_platform/widgets/app_button.dart'; +import 'package:flutter/material.dart'; + +import '../../../../models/workspaces_page/entry.dart'; +import '../../../../utils/colors.dart'; + + +class EntryCard extends StatefulWidget { + final Entry entry; + final void Function() onDelete; + final bool pending; + + const EntryCard({ + super.key, + required this.entry, + required this.onDelete, + required this.pending, + }); + + @override + State createState() => _EntryCardState(); +} + +class _EntryCardState extends State { + final entryController = TextEditingController(); + final entryFocusNode = FocusNode(); + + final double shrunkHeight = 120.0; + final double extendHeight = 450.0; + + bool extended = false; + bool readOnly = true; + + @override + void initState() { + super.initState(); + entryController.text = widget.entry.content; + } + + @override + void dispose() { + entryController.dispose(); + entryFocusNode.dispose(); + super.dispose(); + } + + Widget entryHeader() { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + widget.entry.isTheoremEntry ? "Theorem" + : widget.entry.isProofEntry ? "Proof" + : "", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.0, + color: widget.entry.isTheoremEntry ? Colors.green.shade800 + : widget.entry.isProofEntry ? Colors.yellow.shade800 + : Colors.blue.shade800, + ), + ), + if (widget.entry.isFinalEntry) Text( + " (Final)", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.0, + color: Colors.red.shade800, + ), + ), + ], + ); + } + + Widget iconRow(bool pending) { + return !pending ? Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + readOnly ? IconButton( + onPressed: () { // Make editable + setState(() { + readOnly = false; + }); + }, + icon: const Icon(Icons.edit), + ) : Row( + children: [ + IconButton( + onPressed: () { + // Save the changes + setState(() { + readOnly = true; + }); + }, + icon: const Icon(Icons.check), + ), + IconButton( + onPressed: () { + // Do not save the changes + setState(() { + readOnly = true; + }); + }, + icon: const Icon(Icons.close), + ), + ], + ), + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => + AppAlertDialog( + text: "Do you want to delete the entry?", + actions: [ + AppButton( + text: "Yes", + height: 40, + onTap: () { + setState(() { // delete the entry + widget.onDelete(); + }); + Navigator.of(context).pop(); + }, + ), + AppButton( + text: "No", + height: 40, + onTap: () { Navigator.of(context).pop(); }, + ), + ], + ), + ); + }, + icon: const Icon(Icons.delete), + ), + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AppAlertDialog( + text: "Do you want to finalize the entry?", + actions: [ + AppButton( + text: "Yes", + height: 40, + onTap: () { + /* Finalize the entry */ + Navigator.of(context).pop(); + }, + ), + AppButton( + text: "No", + height: 40, + onTap: () { Navigator.of(context).pop(); }, + ), + ], + ), + ); + }, + icon: const Icon(Icons.stop), + ), + const Expanded(child: SizedBox()), + Text( + widget.entry.publishDateFormatted, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16.0, + color: Colors.grey, + ), + ), + const SizedBox(width: 10.0), + ], + ) : Padding( + padding: const EdgeInsets.only(top: 12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + widget.entry.publishDateFormatted, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16.0, + color: Colors.grey, + ), + ), + const SizedBox(width: 10.0), + ], + ), + ); + } + + Widget fullEntryContent() { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + readOnly: readOnly, + controller: entryController, + focusNode: entryFocusNode, + cursorColor: Colors.grey.shade700, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16.0, + ), + maxLines: 10, + onChanged: (text) { /* What will happen when the text changes? */ }, + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.primaryColor), + borderRadius: BorderRadius.circular(4.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.secondaryDarkColor), + borderRadius: BorderRadius.circular(4.0), + ), + ), + ), + ), + const SizedBox(height: 10.0), + iconRow(widget.pending), + ], + ); + } + + Widget headerOfContent() { + return Text( + widget.entry.content, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + height: extended ? extendHeight : shrunkHeight, + child: Card( + elevation: 4.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 0.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + entryHeader(), + extended ? fullEntryContent() : headerOfContent(), + const Expanded(child: SizedBox()), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: () { + setState(() { + extended = !extended; + }); + }, + icon: extended + ? const Icon(Icons.keyboard_arrow_up) + : const Icon(Icons.keyboard_arrow_down), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/mobile_workspace_content.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/mobile_workspace_content.dart new file mode 100644 index 00000000..9934580c --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/mobile_workspace_content.dart @@ -0,0 +1,339 @@ +import 'package:collaborative_science_platform/screens/workspace_page/mobile_workspace_page/widget/reference_card.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/mobile_workspace_page/widget/subsection_title.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + + +import '../../../../models/node.dart'; +import '../../../../models/user.dart'; +import '../../../../models/workspaces_page/entry.dart'; +import '../../../../models/workspaces_page/workspace.dart'; +import '../../../../providers/auth.dart'; +import '../../../../utils/lorem_ipsum.dart'; +import '../../../../utils/responsive/responsive.dart'; +import '../../../../widgets/app_button.dart'; +import '../../web_workspace_page/widgets/add_reference_form.dart'; +import '../../web_workspace_page/widgets/entry_form.dart'; +import '../../web_workspace_page/widgets/send_collaboration_request_form.dart'; +import 'app_alert_dialog.dart'; +import 'contributor_card.dart'; +import 'entry_card.dart'; + +class MobileWorkspaceContent extends StatefulWidget { + final int workspaceId; + final bool pending; + const MobileWorkspaceContent({ + super.key, + required this.workspaceId, + required this.pending, + }); + + @override + State createState() => _MobileWorkspaceContentState(); +} + +class _MobileWorkspaceContentState extends State { + bool isLoading = false; + bool error = false; + String errorMessage = ""; + + Workspace workspaceData = Workspace( + workspaceId: 0, + workspaceTitle: "workspaceTitle", + entries: [], + status: WorkspaceStatus.workable, + numApprovals: 0, + contributors: [], + pendingContributors: [], + references: [], + ); + + @override + void didChangeDependencies() { + getWorkspaceData(); + super.didChangeDependencies(); + } + + void getWorkspaceData() { + setState(() { + isLoading = true; + }); + workspaceData = Workspace( + workspaceId: 0, + workspaceTitle: "workspaceTitle", + entries: [ + Entry( + content: getLongLoremIpsum(), + entryDate: DateTime.now(), + entryId: 1, + entryNumber: 1, + index: 1, + isEditable: false, + isFinalEntry: false, + isProofEntry: false, + isTheoremEntry: true, + ), + Entry( + content: getLongLoremIpsum(2), + entryDate: DateTime.now(), + entryId: 2, + entryNumber: 2, + index: 2, + isEditable: false, + isFinalEntry: false, + isProofEntry: true, + isTheoremEntry: false, + ), + Entry( + content: getLongLoremIpsum(3), + entryDate: DateTime.now(), + entryId: 2, + entryNumber: 2, + index: 2, + isEditable: false, + isFinalEntry: true, + isProofEntry: true, + isTheoremEntry: false, + ), + ], + status: WorkspaceStatus.workable, + numApprovals: 0, + contributors: [ + // Automatically add the user to the list of contributors + // It will be deleted once the providers are implemented + if (!widget.pending) Provider.of(context).user as User, + User( + email: "dummy1@mail.com", + firstName: "dummy 1", + lastName: "jackson", + ), + User( + email: "dummy2@mail.com", + firstName: "dummy 2", + lastName: "jackson", + ), + ], + pendingContributors: [ + User( + email: "dummy3@mail.com", + firstName: "dummy 3", + lastName: "jackson", + ), + ], + references: [ + Node( + contributors: [ + User( + email: "dummy1@mail.com", + firstName: "dummy 1", + lastName: "jackson", + ), + User( + email: "dummy2@mail.com", + firstName: "dummy 2", + lastName: "jackson", + ), + ], + id: 1, + nodeTitle: "Awesome Node Title", + publishDate: DateTime.now(), + ), + ], + ); + setState(() { + isLoading = false; + }); + } + + Widget addIcon(Function() onPressed) { + return Center( + child: IconButton( + iconSize: 40.0, + onPressed: onPressed, + icon: const Icon(Icons.add), + ), + ); + } + + Widget firstAddition(String message, Function() onPressed) { + return ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + children: [ + Center( + child: Text( + message, + style: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + ), + ), + addIcon(onPressed), + ], + ); + } + + Widget entryList() { + int length = workspaceData.entries.length; + Widget alertDialog = AppAlertDialog( + text: 'New Entry', + content: const EntryForm(newEntry: true), + actions: [ + AppButton( + text: "Create New Entry", + height: 40, + onTap: () { + /* Create Entry */ + Navigator.of(context).pop(); + }, + ), + ], + ); + + return workspaceData.entries.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: ListView.builder( + padding: const EdgeInsets.all(0.0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: length + 1, + itemBuilder: (context, index) => (index < length) + ? EntryCard( + entry: workspaceData.entries[index], + onDelete: () { + setState(() { + workspaceData.entries.removeAt(index); + }); + }, + pending: widget.pending, + ) : addIcon(() { + showDialog( + context: context, + builder: (context) => alertDialog + ); + }), + ), + ) : firstAddition( + "Add Your First Entry!", + () { + showDialog( + context: context, + builder: (context) => alertDialog + ); + }, + ); + } + + Widget contributorList() { + int length = workspaceData.contributors.length; + Widget alertDialog = const AppAlertDialog( + text: "Send Collaboration Request", + content: SendCollaborationRequestForm(), + ); + + return workspaceData.contributors.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: ListView.builder( + padding: const EdgeInsets.all(0.0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: length + 1, + itemBuilder: (context, index) => (index < length) + ? ContributorCard(contributor: workspaceData.contributors[index]) + : addIcon(() { + showDialog( + context: context, + builder: (context) => alertDialog + ); + } + ), + ), + ) : firstAddition( + "Add The First Contributor!", + () { + showDialog( + context: context, + builder: (context) => alertDialog + ); + }, + ); + } + + Widget referenceList() { + int length = workspaceData.references.length; + Widget alertDialog = const AppAlertDialog + (text: "Add Reference", + content: AddReferenceForm(), + ); + + return (workspaceData.references.isNotEmpty) + ? Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: ListView.builder( + padding: const EdgeInsets.all(0.0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: length + 1, + itemBuilder: (context, index) => (index < length) + ? ReferenceCard(reference: workspaceData.references[index]) + : addIcon(() { + showDialog( + context: context, + builder: (context) => alertDialog + ); + }), + ), + ) : firstAddition( + "Add Your First Reference!", + () { + showDialog( + context: context, + builder: (context) => alertDialog + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + print("Created ${widget.workspaceId}"); + if (isLoading || error) { + return Center( + child: isLoading ? const CircularProgressIndicator() + : error ? SelectableText(errorMessage) + : const SelectableText("Something went wrong!") + ); + } else { + return SizedBox( + width: Responsive.getGenericPageWidth(context), + child: ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0.0), + // It needs to be nested scrollable in the future + children: [ + const SizedBox(height: 10.0), + const SubSectionTitle(title: "Entries"), + entryList(), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Divider(), + ), + const SubSectionTitle(title: "Contributors"), + contributorList(), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 12.0), + child: Divider(), + ), + const SubSectionTitle(title: "References"), + referenceList(), + const SizedBox(height: 20.0), + ], + ), + ); + } + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/reference_card.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/reference_card.dart new file mode 100644 index 00000000..8d86161b --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/reference_card.dart @@ -0,0 +1,96 @@ +import 'package:collaborative_science_platform/models/node.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import '../../../node_details_page/node_details_page.dart'; + +class ReferenceCard extends StatelessWidget { + final Node reference; + const ReferenceCard({ + super.key, + required this.reference, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), + child: SizedBox( + height: 100.0, + child: Card( + elevation: 4.0, + //shadowColor: AppColors.primaryColor, + //color: AppColors.primaryLightColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: InkWell( + onTap: () { + // Navigate to the node page of the theorem + context.push('${NodeDetailsPage.routeName}/${reference.id}'); + }, + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(width: 4.0), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + reference.nodeTitle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 18.0, + ), + ), + const SizedBox(height: 2.0), + Text( + "By ${reference.contributors.map( + (user) => "${user.firstName} ${user.lastName}" + ).join(", ")}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16.0, + color: Colors.grey, + ), + ), + const SizedBox(height: 2.0), + Text( + reference.publishDateFormatted, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16.0, + color: Colors.grey, + ), + // textAlign: TextAlign.start, + ), + ], + ), + const Expanded(child: SizedBox(width: 4.0)), + IconButton( + onPressed: () { //remove reference + + }, + icon: const Icon(Icons.delete), + ) + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/subsection_title.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/subsection_title.dart new file mode 100644 index 00000000..9944b17f --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/subsection_title.dart @@ -0,0 +1,29 @@ +import 'package:collaborative_science_platform/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class SubSectionTitle extends StatelessWidget { + final String title; + const SubSectionTitle({ + super.key, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: Center( + child: Text( + title, + style: const TextStyle( + color: AppColors.secondaryDarkColor, + fontWeight: FontWeight.w600, + fontSize: 22.0, + ), + ), + ), + ); + } +} + + diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/web_workspace_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/web_workspace_page.dart new file mode 100644 index 00000000..c1aaaf86 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/web_workspace_page.dart @@ -0,0 +1,207 @@ +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/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/screens/page_with_appbar/widgets/app_bar_button.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/contributors_list_view.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/entries_list_view.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/references_list_view.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/workspaces_side_bar.dart'; +import 'package:collaborative_science_platform/utils/text_styles.dart'; +import 'package:collaborative_science_platform/widgets/app_button.dart'; +import 'package:flutter/cupertino.dart'; + +import 'package:flutter/material.dart'; + +class WebWorkspacePage extends StatefulWidget { + final Workspace? workspace; + final Workspaces? workspaces; + final bool isLoading; + + const WebWorkspacePage( + {super.key, required this.workspace, required this.workspaces, required this.isLoading}); + + @override + State createState() => _WebWorkspacePageState(); +} + +class _WebWorkspacePageState extends State { + ScrollController controller1 = ScrollController(); + ScrollController controller2 = ScrollController(); + ScrollController controller3 = ScrollController(); + ScrollController controller4 = ScrollController(); + + bool _isFirstTime = true; + + bool error = false; + String errorMessage = ""; + + bool showSidebar = true; + double minHeight = 750; + + @override + void dispose() { + controller1.dispose(); + controller2.dispose(); + controller3.dispose(); + controller4.dispose(); + super.dispose(); + } + + @override + void didChangeDependencies() { + if (_isFirstTime) { + _isFirstTime = false; + } + if (MediaQuery.of(context).size.height > 750) { + setState(() { + minHeight = MediaQuery.of(context).size.height; + }); + } + + super.didChangeDependencies(); + } + + hideSideBar() { + setState(() { + showSidebar = false; + }); + } + + @override + Widget build(BuildContext context) { + return PageWithAppBar( + appBar: const HomePageAppBar(), + pageColor: Colors.grey.shade200, + child: widget.isLoading + ? Container( + padding: const EdgeInsets.only(top: 32), + decoration: const BoxDecoration(color: Colors.white), + child: const Center( + child: CircularProgressIndicator(), + ), + ) + : error + ? Text(errorMessage, style: const TextStyle(color: Colors.red)) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showSidebar) + WorkspacesSideBar( + controller: controller1, + hideSidebar: hideSideBar, + height: minHeight, + workspaces: widget.workspaces, + ), + if (!showSidebar) + Container( + height: minHeight + 100, + width: MediaQuery.of(context).size.width * 0.05, + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.8), + blurRadius: 7, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + const SizedBox( + height: 20, + ), + AppBarButton( + onPressed: () { + setState(() { + showSidebar = true; + }); + }, + icon: CupertinoIcons.forward, + text: "hide workspaces", + ), + const SizedBox( + height: 20, + ), + const RotatedBox( + quarterTurns: 3, + child: Text( + "Show Workspaces", + textAlign: TextAlign.end, + style: TextStyles.title4black, + ), + ) + ], + )), + if (widget.workspace != null) + Column( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.75, + height: 100, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text(widget.workspace!.workspaceTitle, style: TextStyles.title2), + SizedBox( + width: MediaQuery.of(context).size.width / 5, + child: AppButton( + text: "Send Workspace to Review", + height: 45, + onTap: () {}, + type: "primary", + ), + ), + ], + ), + ), + Row( + children: [ + EntriesListView( + entries: widget.workspace!.entries, + controller: controller2, + showSidebar: showSidebar, + height: minHeight, + ), + Column( + children: [ + ContributorsListView( + contributors: widget.workspace!.contributors, + pendingContributors: widget.workspace!.pendingContributors, + controller: controller3, + height: minHeight / 2, + ), + ReferencesListView( + references: widget.workspace!.references, + controller: controller4, + height: minHeight / 2, + ), + ], + ) + ], + ) + ], + ) + else + SizedBox( + width: showSidebar + ? MediaQuery.of(context).size.width * 0.75 + : MediaQuery.of(context).size.width * 0.95, + height: minHeight, + child: const Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Select a workspace to see details.", + style: TextStyles.title2, + ) + ]), + ) + ], + )); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/add_reference_form.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/add_reference_form.dart new file mode 100644 index 00000000..4f32b5d3 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/add_reference_form.dart @@ -0,0 +1,146 @@ +import 'package:collaborative_science_platform/providers/node_provider.dart'; +import 'package:collaborative_science_platform/utils/text_styles.dart'; +import 'package:collaborative_science_platform/widgets/app_search_bar.dart'; +import 'package:collaborative_science_platform/widgets/card_container.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class AddReferenceForm extends StatefulWidget { + const AddReferenceForm({super.key}); + + @override + State createState() => _AddReferenceFormState(); +} + +class _AddReferenceFormState extends State { + final searchBarFocusNode = FocusNode(); + bool isLoading = false; + bool firstSearch = false; + + bool error = false; + String errorMessage = ""; + + @override + void dispose() { + searchBarFocusNode.dispose(); + super.dispose(); + } + + void search(String text) async { + SearchType searchType = SearchType.theorem; + if (text.isEmpty) return; + try { + final nodeProvider = Provider.of(context, listen: false); + setState(() { + isLoading = true; + firstSearch = true; + }); + await nodeProvider.search(searchType, text); + } catch (e) { + setState(() { + error = true; + errorMessage = "Something went wrong!"; + }); + } finally { + setState(() { + isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + final nodeProvider = Provider.of(context); + return SizedBox( + height: 600, + child: SingleChildScrollView( + primary: false, + scrollDirection: Axis.vertical, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(10.0, 16.0, 8.0, 0.0), + child: AppSearchBar( + hintText: "Search Theorem", + focusNode: searchBarFocusNode, + onSearch: search, + hideSearchType: true, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 18.0), + child: isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : SizedBox( + height: 500, + width: 500, + child: ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + padding: const EdgeInsets.fromLTRB(3.0, 3.0, 3.0, 50.0), + itemCount: nodeProvider.searchNodeResult.length, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.all(3), + child: CardContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + nodeProvider.searchNodeResult[index].nodeTitle, + style: TextStyles.bodyBold, + textAlign: TextAlign.start, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + Text( + nodeProvider.searchNodeResult[index].contributors + .map((e) => "${e.firstName} ${e.lastName}") + .join(", "), + style: TextStyles.bodyGrey, + textAlign: TextAlign.start, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + Text( + nodeProvider.searchNodeResult[index].publishDateFormatted, + style: TextStyles.bodyGrey, + textAlign: TextAlign.start, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + IconButton( + onPressed: () { + //add reference + }, + icon: Icon( + Icons.add, + color: Colors.grey[600], + ), + ), + ], + ), + ), + ); + }), + ), + ), + ], + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/contributors_list_view.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/contributors_list_view.dart new file mode 100644 index 00000000..b47bfb13 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/contributors_list_view.dart @@ -0,0 +1,135 @@ +import 'package:collaborative_science_platform/models/user.dart'; +import 'package:collaborative_science_platform/screens/profile_page/profile_page.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/send_collaboration_request_form.dart'; +import 'package:collaborative_science_platform/utils/colors.dart'; +import 'package:collaborative_science_platform/utils/text_styles.dart'; +import 'package:collaborative_science_platform/widgets/app_button.dart'; +import 'package:collaborative_science_platform/widgets/card_container.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../mobile_workspace_page/widget/app_alert_dialog.dart'; + +class ContributorsListView extends StatelessWidget { + final List contributors; + final List pendingContributors; + final ScrollController controller; + final double height; + const ContributorsListView( + {super.key, + required this.contributors, + required this.pendingContributors, + required this.controller, + required this.height}); + + @override + Widget build(BuildContext context) { + return Container( + height: height, + width: MediaQuery.of(context).size.width / 4, + decoration: BoxDecoration(color: Colors.grey[100]), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ + const Text("Contributors", style: TextStyles.title4secondary), + SizedBox( + height: (height * 2) / 3, + child: ListView.builder( + controller: controller, + scrollDirection: Axis.vertical, + shrinkWrap: true, + padding: const EdgeInsets.all(3), + itemCount: contributors.length + pendingContributors.length, + itemBuilder: (BuildContext context, int index) { + if (index < contributors.length) { + return Padding( + padding: const EdgeInsets.all(3), + child: CardContainer( + onTap: () { + final String email = contributors[index].email; + final String encodedEmail = Uri.encodeComponent(email); + context.push('${ProfilePage.routeName}/$encodedEmail'); + }, + child: Column( + children: [ + Text( + "${contributors[index].firstName} ${contributors[index].lastName}", + style: TextStyles.bodyBold, + ), + Text( + contributors[index].email, + style: TextStyles.bodyGrey, + ) + ], + ), + ), + ); + } else { + return Padding( + padding: const EdgeInsets.all(3), + child: CardContainer( + onTap: () { + final String email = + pendingContributors[index - contributors.length].email; + final String encodedEmail = Uri.encodeComponent(email); + context.push('${ProfilePage.routeName}/$encodedEmail'); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width / 8, + child: Column( + children: [ + Text( + "${pendingContributors[index - contributors.length].firstName} ${pendingContributors[index - contributors.length].lastName}", + style: TextStyles.bodyBold, + ), + Text( + pendingContributors[index - contributors.length].email, + style: TextStyles.bodyGrey, + ) + ], + ), + ), + Column(children: [ + IconButton( + icon: const Icon( + CupertinoIcons.clear_circled, + color: AppColors.warningColor, + ), + onPressed: () { + // function to delete collaboration request + }, + ), + ]) + ], + ), + ), + ); + } + + }), + ), + SizedBox( + width: MediaQuery.of(context).size.width / 6, + child: AppButton( + text: "Send Collaboration Request", + height: 40, + type: "outlined", + onTap: () { + showDialog( + context: context, + builder: (context) => const AppAlertDialog( + text: "Send Collaboration Request", + content: SendCollaborationRequestForm(), + ), + ); + }, + ), + ), + ])), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/create_workspace_form.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/create_workspace_form.dart new file mode 100644 index 00000000..1b4d187b --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/create_workspace_form.dart @@ -0,0 +1,42 @@ +import 'package:collaborative_science_platform/widgets/app_text_field.dart'; +import 'package:flutter/material.dart'; + +class CreateWorkspaceForm extends StatefulWidget { + const CreateWorkspaceForm({super.key}); + + @override + State createState() => _CreateWorkspaceFormState(); +} + +class _CreateWorkspaceFormState extends State { + final titleController = TextEditingController(); + + final titleFocusNode = FocusNode(); + + @override + void dispose() { + titleController.dispose(); + titleFocusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 100, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AppTextField( + controller: titleController, + focusNode: titleFocusNode, + hintText: 'Workspace Title', + obscureText: false, + height: 64, + ), + ], + )); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/entries_list_view.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/entries_list_view.dart new file mode 100644 index 00000000..8029fddf --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/entries_list_view.dart @@ -0,0 +1,165 @@ +import 'package:collaborative_science_platform/models/workspaces_page/entry.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/entry_form.dart'; +import 'package:collaborative_science_platform/utils/text_styles.dart'; +import 'package:collaborative_science_platform/widgets/app_button.dart'; +import 'package:collaborative_science_platform/widgets/card_container.dart'; +import 'package:flutter/material.dart'; + +import '../../mobile_workspace_page/widget/app_alert_dialog.dart'; + +class EntriesListView extends StatelessWidget { + final List entries; + final ScrollController controller; + final bool showSidebar; + final double height; + const EntriesListView( + {super.key, + required this.entries, + required this.controller, + required this.showSidebar, + required this.height}); + + @override + Widget build(BuildContext context) { + return Container( + height: height, + width: showSidebar + ? MediaQuery.of(context).size.width / 2 + : MediaQuery.of(context).size.width * 0.7, + decoration: BoxDecoration(color: Colors.grey.withOpacity(0.1)), + child: Padding( + padding: const EdgeInsets.all(16), + child: (Column( + children: [ + Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ + const Text( + "Entries", + style: TextStyles.title3secondary, + ), + SizedBox( + width: MediaQuery.of(context).size.width / 6, + child: AppButton( + text: "Create New Entry", + height: 40, + onTap: () { + showDialog( + context: context, + builder: (context) => AppAlertDialog( + text: "New Entry", + content: const EntryForm(newEntry: true), + actions: [ + AppButton( + text: "Create New Entry", + height: 40, + onTap: () { /* Create new Entry */ }, + ), + ], + ), + ); + }, + type: "outlined", + ), + ), + ]), + if (entries.length > 0) + ListView.builder( + controller: controller, + scrollDirection: Axis.vertical, + shrinkWrap: true, + padding: const EdgeInsets.all(8), + itemCount: entries.length, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.all(5), + child: CardContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + entries[index].isProofEntry + ? "Proof" + : (entries[index].isTheoremEntry ? "Theorem" : ""), + style: TextStyles.bodyGrey, + textAlign: TextAlign.start, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (entries[index].isEditable) + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => AppAlertDialog( + text: "Edit Entry", + content: EntryForm(id: entries[index].entryId), + actions: [ + AppButton( + text: "Save Entry", + height: 40, + onTap: () { /* Edit the Entry */ }, + ), + ], + ), + ); + }, + icon: Icon( + Icons.edit, + color: Colors.grey[600], + )), + if (entries[index].isEditable) + IconButton( + onPressed: () { + //edit entry + }, + icon: Icon( + Icons.delete, + color: Colors.grey[600], + )) + ], + ) + ], + ), + Text( + entries[index].content, + style: TextStyles.bodyBlack, + textAlign: TextAlign.start, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + entries[index].isFinalEntry ? "Finalized" : "", + style: TextStyles.bodyGrey, + ), + Text( + entries[index].publishDateFormatted, + style: TextStyles.bodyGrey, + textAlign: TextAlign.end, + ), + ], + ), + ], + ), + ), + ); + }) + else + const Padding( + padding: EdgeInsets.all(16), + child: Text( + "No Entries Yet!", + style: TextStyles.bodyGrey, + ), + ) + + ], + )), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/entry_form.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/entry_form.dart new file mode 100644 index 00000000..30bc0590 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/entry_form.dart @@ -0,0 +1,49 @@ +import 'package:collaborative_science_platform/widgets/app_text_field.dart'; +import 'package:flutter/material.dart'; + +class EntryForm extends StatefulWidget { + final int id; + final bool newEntry; + const EntryForm({super.key, this.id = 0, this.newEntry = false}); + + @override + State createState() => _EntryFormState(); +} + +class _EntryFormState extends State { + final contentController = TextEditingController(); + + final contentFocusNode = FocusNode(); + @override + void dispose() { + contentController.dispose(); + contentFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 300, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10.0), + Expanded( + child: SizedBox( + child: AppTextField( + controller: contentController, + focusNode: contentFocusNode, + hintText: "Content", + obscureText: false, + height: 200, + maxLines: 10, + ), + ), + ) + ], + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/references_list_view.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/references_list_view.dart new file mode 100644 index 00000000..5000684f --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/references_list_view.dart @@ -0,0 +1,110 @@ +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/screens/workspace_page/mobile_workspace_page/widget/app_alert_dialog.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/add_reference_form.dart'; +import 'package:collaborative_science_platform/utils/text_styles.dart'; +import 'package:collaborative_science_platform/widgets/app_button.dart'; +import 'package:collaborative_science_platform/widgets/card_container.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class ReferencesListView extends StatelessWidget { + final List references; + final ScrollController controller; + final double height; + const ReferencesListView( + {super.key, required this.references, required this.controller, required this.height}); + + @override + Widget build(BuildContext context) { + return Container( + height: height, + width: MediaQuery.of(context).size.width / 4, + decoration: BoxDecoration(color: Colors.grey[100]), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + const Text( + "References", + style: TextStyles.title4secondary, + ), + SizedBox( + width: MediaQuery.of(context).size.width / 6, + child: AppButton( + text: "Add References", + height: 40, + type: "outlined", + onTap: () { + showDialog( + context: context, + builder: (context) => const AppAlertDialog( + text: "Add References", + content: AddReferenceForm(), + ), + ); + }, + ), + ), + SizedBox( + height: (height * 2) / 3, + child: ListView.builder( + controller: controller, + scrollDirection: Axis.vertical, + shrinkWrap: true, + padding: const EdgeInsets.all(3), + itemCount: references.length, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.all(3), + child: CardContainer( + onTap: () { + context.push("${NodeDetailsPage.routeName}/${references[index].id}"); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width / 8, + child: Text( + references[index].nodeTitle, + style: TextStyles.bodyBold, + textAlign: TextAlign.start, + ), + ), + IconButton( + onPressed: () { + //remove reference + }, + icon: Icon( + Icons.delete, + color: Colors.grey[600], + )) + ], + ), + Text( + references[index] + .contributors + .map((e) => "by ${e.firstName} ${e.lastName}") + .join(", "), + style: TextStyles.bodyGrey, + textAlign: TextAlign.start, + ), + Text( + references[index].publishDateFormatted, + style: TextStyles.bodyGrey, + textAlign: TextAlign.start, + ), + ], + ), + ), + ); + }), + ), + ]), + )); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/send_collaboration_request_form.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/send_collaboration_request_form.dart new file mode 100644 index 00000000..f218788d --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/send_collaboration_request_form.dart @@ -0,0 +1,135 @@ +import 'package:collaborative_science_platform/providers/user_provider.dart'; +import 'package:collaborative_science_platform/utils/text_styles.dart'; +import 'package:collaborative_science_platform/widgets/app_search_bar.dart'; +import 'package:collaborative_science_platform/widgets/card_container.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SendCollaborationRequestForm extends StatefulWidget { + const SendCollaborationRequestForm({super.key}); + + @override + State createState() => _SendCollaborationRequestFormState(); +} + +class _SendCollaborationRequestFormState extends State { + final searchBarFocusNode = FocusNode(); + bool isLoading = false; + bool firstSearch = false; + + bool error = false; + String errorMessage = ""; + + @override + void dispose() { + searchBarFocusNode.dispose(); + super.dispose(); + } + + void search(String text) async { + SearchType searchType = SearchType.author; + if (text.isEmpty) return; + try { + final userProvider = Provider.of(context, listen: false); + setState(() { + isLoading = true; + firstSearch = true; + }); + await userProvider.search(searchType, text); + } catch (e) { + setState(() { + error = true; + errorMessage = "Something went wrong!"; + }); + } finally { + setState(() { + isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + final userProvider = Provider.of(context); + return SizedBox( + height: 600, + child: SingleChildScrollView( + primary: false, + scrollDirection: Axis.vertical, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(10.0, 16.0, 8.0, 0.0), + child: AppSearchBar( + hintText: "Search User", + focusNode: searchBarFocusNode, + onSearch: search, + hideSearchType: true, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 18.0), + child: isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : SizedBox( + height: 500, + width: 500, + child: ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + padding: const EdgeInsets.fromLTRB(3.0, 3.0, 3.0, 50.0), + itemCount: userProvider.searchUserResult.length, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.all(3), + child: CardContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${userProvider.searchUserResult[index].name} ${userProvider.searchUserResult[index].surname}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyles.bodyBold, + ), + Text( + userProvider.searchUserResult[index].email, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyles.bodyGrey, + ) + ], + ), + ), + IconButton( + onPressed: () { + // send collaboration request + }, + icon: Icon( + Icons.send, + color: Colors.grey[600], + ), + ), + ], + ), + ), + ); + }), + ), + ), + ], + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/workspaces_side_bar.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/workspaces_side_bar.dart new file mode 100644 index 00000000..209170bf --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/workspaces_side_bar.dart @@ -0,0 +1,171 @@ +import 'package:collaborative_science_platform/models/workspaces_page/workspaces.dart'; +import 'package:collaborative_science_platform/screens/page_with_appbar/widgets/app_bar_button.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/widgets/create_workspace_form.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/workspaces_page.dart'; +import 'package:collaborative_science_platform/utils/colors.dart'; +import 'package:collaborative_science_platform/utils/text_styles.dart'; +import 'package:collaborative_science_platform/widgets/app_button.dart'; +import 'package:collaborative_science_platform/widgets/card_container.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:go_router/go_router.dart'; +import 'package:flutter/material.dart'; + +class WorkspacesSideBar extends StatefulWidget { + final ScrollController controller; + final Function? hideSidebar; + final double height; + final Workspaces? workspaces; + + const WorkspacesSideBar( + {super.key, + required this.controller, + this.hideSidebar, + required this.height, + this.workspaces}); + + @override + State createState() => _WorkspacesSideBarState(); +} + +class _WorkspacesSideBarState extends State { + @override + Widget build(BuildContext context) { + return Container( + height: widget.height + 100, + width: MediaQuery.of(context).size.width / 4, + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.8), + blurRadius: 7, + offset: const Offset(0, 2), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const Text("My Workspaces", style: TextStyles.title3secondary), + AppBarButton( + onPressed: () { + widget.hideSidebar!(); + }, + icon: CupertinoIcons.back, + text: "hide workspaces", + ) + ], + ), + SizedBox( + width: MediaQuery.of(context).size.width / 6, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: AppButton( + text: "Create New Workspace", + height: 40, + onTap: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const SizedBox( + width: 500, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('New Workspace', style: TextStyle(fontSize: 20.0)), + ], + ), + ), + backgroundColor: Colors.white, + surfaceTintColor: Colors.white, + content: const CreateWorkspaceForm(), + actions: [ + AppButton( + text: "Create New Workspace", height: 50, onTap: () {}) + ], + )); + }, + type: "outlined", + )), + ), + SizedBox( + height: (widget.workspaces != null) ? widget.height * 0.9 : 40, + child: (widget.workspaces != null) + ? ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + padding: const EdgeInsets.all(8), + itemCount: (widget.workspaces!.workspaces.length + + widget.workspaces!.pendingWorkspaces.length), + itemBuilder: (BuildContext context, int index) { + if (index < widget.workspaces!.workspaces.length) { + return Padding( + padding: const EdgeInsets.all(5), + child: CardContainer( + onTap: () { + context.push( + "${WorkspacesPage.routeName}/${widget.workspaces!.workspaces[index].workspaceId}"); + }, + child: Text( + widget.workspaces!.workspaces[index].workspaceTitle, + style: TextStyles.title4, + textAlign: TextAlign.start, + ), + ), + ); + } else if (index >= widget.workspaces!.workspaces.length) { + return Padding( + padding: const EdgeInsets.all(5), + child: CardContainer( + onTap: () { + context.push( + "${WorkspacesPage.routeName}/${widget.workspaces!.pendingWorkspaces[index - widget.workspaces!.workspaces.length].workspaceId}"); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + widget + .workspaces! + .pendingWorkspaces[ + index - widget.workspaces!.workspaces.length] + .workspaceTitle, + style: TextStyles.bodyBold, + textAlign: TextAlign.start, + ), + Column(children: [ + IconButton( + icon: const Icon(CupertinoIcons.check_mark_circled, + color: AppColors.infoColor), + onPressed: () { + // function to accept collaboration request + }, + ), + IconButton( + icon: const Icon( + CupertinoIcons.clear_circled, + color: AppColors.warningColor, + ), + onPressed: () { + // function to reject collaboration request + }, + ), + ]) + ], + )), + ); + } else { + return const SizedBox(); + } + }) + : const CircularProgressIndicator(), + ), + ], + ))); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/workspaces_page.dart b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/workspaces_page.dart new file mode 100644 index 00000000..a9ada4e1 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/workspaces_page.dart @@ -0,0 +1,116 @@ +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/providers/auth.dart'; +import 'package:collaborative_science_platform/providers/workspace_provider.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/web_workspace_page/web_workspace_page.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../utils/responsive/responsive.dart'; +import 'mobile_workspace_page/mobile_workspace_page.dart'; + +class WorkspacesPage extends StatefulWidget { + static const routeName = '/workspaces'; + final int workspaceId; + const WorkspacesPage({super.key, this.workspaceId = -1}); + @override + State createState() => _WorkspacesPageState(); +} + +class _WorkspacesPageState extends State { + bool _isFirstTime = true; + bool error = false; + String errorMessage = ""; + + bool isLoading = false; + + Workspace? workspace; + Workspaces? workspaces; + + void getWorkspaceById(int id) async { + try { + final workspaceProvider = Provider.of(context); + final auth = Provider.of(context); + setState(() { + error = false; + isLoading = true; + }); + await workspaceProvider.getWorkspaceById(id, auth.user!.token); + setState(() { + workspace = (workspaceProvider.workspace ?? {} as Workspace); + }); + } on WorkspaceDoesNotExist { + setState(() { + error = true; + errorMessage = WorkspaceDoesNotExist().message; + }); + } catch (e) { + setState(() { + error = true; + errorMessage = "Something went wrong!"; + }); + } finally { + setState(() { + isLoading = false; + }); + } + } + + void getUserWorkspaces() async { + try { + final auth = Provider.of(context); + final workspaceProvider = Provider.of(context); + setState(() { + error = false; + isLoading = true; + }); + await workspaceProvider.getUserWorkspaces(auth.basicUser!.basicUserId, auth.user!.token); + setState(() { + workspaces = (workspaceProvider.workspaces ?? {} as Workspaces); + }); + } on WorkspaceDoesNotExist { + setState(() { + error = true; + errorMessage = WorkspaceDoesNotExist().message; + }); + } catch (e) { + setState(() { + error = true; + errorMessage = "Something went wrong!"; + }); + } finally { + setState(() { + isLoading = false; + }); + } + } + + @override + void didChangeDependencies() { + if (_isFirstTime) { + if (widget.workspaceId > -1) { + getWorkspaceById(widget.workspaceId); + } + getUserWorkspaces(); + + _isFirstTime = false; + } + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + return Responsive( + mobile: MobileWorkspacePage( + workspace: workspace, + workspaces: workspaces, + ), + desktop: WebWorkspacePage( + isLoading: isLoading, + workspace: workspace, + workspaces: workspaces, + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/services/screen_navigation.dart b/project/FrontEnd/collaborative_science_platform/lib/services/screen_navigation.dart new file mode 100644 index 00000000..78dacf05 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/services/screen_navigation.dart @@ -0,0 +1,70 @@ +import 'package:collaborative_science_platform/screens/auth_screens/please_login_page.dart'; +import 'package:collaborative_science_platform/screens/graph_page/graph_page.dart'; +import 'package:collaborative_science_platform/screens/home_page/home_page.dart'; +import 'package:collaborative_science_platform/screens/notifications_page/notifications_page.dart'; +import 'package:collaborative_science_platform/screens/profile_page/profile_page.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/workspaces_page.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../screens/workspace_page/create_workspace_page/mobile_create_workspace_page.dart'; + +enum ScreenTab { + home, + graph, + workspaces, + workspace, + createWorkspace, + notifications, + profile, + pleaseLogin, + none +} + +class ScreenNavigation extends ChangeNotifier { + ScreenTab _selectedTab = ScreenTab.home; + static final GlobalKey navigatorKey = GlobalKey(); + + ScreenTab get selectedTab => _selectedTab; + + void changeSelectedTab(ScreenTab tab) { + _selectedTab = tab; + } + + void setSelectedTab(ScreenTab tab, BuildContext context, {String? email}) { + _selectedTab = tab; + switch (tab) { + case ScreenTab.home: + context.go(HomePage.routeName); + break; + case ScreenTab.graph: + context.go(GraphPage.routeName); + break; + case ScreenTab.workspaces: // Goes to the page where workspace names are listed + context.go(WorkspacesPage.routeName); + break; + case ScreenTab.workspace: // Goes to the page where details of a workspace are listed + context.go(WorkspacesPage.routeName); + break; + case ScreenTab.createWorkspace: // Goes to the page where workspaces are created + context.go(MobileCreateWorkspacePage.routeName); + break; + case ScreenTab.notifications: + context.go(NotificationPage.routeName); + break; + case ScreenTab.profile: + if (email == "") { + context.go(ProfilePage.routeName); + } else { + context.go('${ProfilePage.routeName}/$email'); + } + break; + case ScreenTab.pleaseLogin: + context.go(PleaseLoginPage.routeName); + break; + case ScreenTab.none: + break; + } + notifyListeners(); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/services/share_page.dart b/project/FrontEnd/collaborative_science_platform/lib/services/share_page.dart new file mode 100644 index 00000000..18942da9 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/services/share_page.dart @@ -0,0 +1,11 @@ +import 'package:collaborative_science_platform/models/node_details_page/node_detailed.dart'; +import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart'; +import 'package:collaborative_science_platform/utils/constants.dart'; +import 'package:share_plus/share_plus.dart'; + +class SharePage { + static void shareNodeView(NodeDetailed node) { + Share.share( + 'Check out this on Collaborative Science Platform: ${node.nodeTitle} at ${Constants.appUrl}/${NodeDetailsPage.routeName}/${node.nodeId}'); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/utils/colors.dart b/project/FrontEnd/collaborative_science_platform/lib/utils/colors.dart new file mode 100644 index 00000000..39ff6347 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/utils/colors.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class AppColors { + static const Color primaryColor = Color(0xFF089BAB); + static const Color primaryLightColor = Color(0xFFb3ecf3); + static const Color primaryDarkColor = Color(0xFF036368); + static const Color secondaryColor = Color(0xFFFEAD54); + static const Color secondaryLightColor = Color(0xFFffe1bd); + static const Color secondaryDarkColor = Color(0xFFef8641); + static const Color tertiaryColor = Color(0xFFE5E5E5); + + static const Color successColor = Color(0xFF28A745); + static const Color warningColor = Color(0xFFFFC107); + static const Color dangerColor = Color(0xFFDC3545); + static const Color infoColor = Color(0xFF17A2B8); + + static const Color hyperTextColor = Color.fromARGB(255, 52, 75, 201); +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/utils/constants.dart b/project/FrontEnd/collaborative_science_platform/lib/utils/constants.dart new file mode 100644 index 00000000..e5eb0ae5 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/utils/constants.dart @@ -0,0 +1,5 @@ +class Constants { + static const String appName = "Collaborative Science Platform"; + static const String appUrl = "http://"; + static const String apiUrl = "http://13.51.55.11:8000/api"; +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/utils/lorem_ipsum.dart b/project/FrontEnd/collaborative_science_platform/lib/utils/lorem_ipsum.dart new file mode 100644 index 00000000..cc67a16f --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/utils/lorem_ipsum.dart @@ -0,0 +1,30 @@ + +String getLoremIpsum([int? index]) { + String substring = (index != null) ? "$index" : ""; + return "Lorem ipsum $substring 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."; +} + +String getLongLoremIpsum([int? index]) { + String substring = (index != null) ? "$index" : ""; + return """Lorem $substring ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Auctor eu augue ut lectus arcu. Enim facilisis gravida neque convallis a cras. Est sit amet facilisis magna etiam tempor orci eu lobortis. Vivamus at augue eget arcu dictum varius duis at. Eros in cursus turpis massa tincidunt dui. Congue nisi vitae suscipit tellus. Nibh praesent tristique magna sit amet purus gravida quis. Cursus turpis massa tincidunt dui. Amet dictum sit amet justo. Velit laoreet id donec ultrices tincidunt arcu non. Turpis massa tincidunt dui ut ornare lectus sit amet. +Id consectetur purus ut faucibus pulvinar elementum integer enim. Mauris nunc congue nisi vitae suscipit tellus mauris. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Arcu dictum varius duis at consectetur lorem. Magna sit amet purus gravida quis blandit turpis cursus in. Sociis natoque penatibus et magnis. Sit amet porttitor eget dolor morbi non arcu risus quis. Augue mauris augue neque gravida in fermentum et sollicitudin ac. Neque egestas congue quisque egestas diam. Suspendisse interdum consectetur libero id. Aliquam vestibulum morbi blandit cursus risus at ultrices. Nam aliquam sem et tortor consequat id porta. Quam vulputate dignissim suspendisse in est ante in nibh. Quis enim lobortis scelerisque fermentum dui faucibus in ornare. Ac ut consequat semper viverra nam libero justo laoreet sit. Nisl vel pretium lectus quam id. Elementum nisi quis eleifend quam adipiscing. Scelerisque eu ultrices vitae auctor. +Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper. Donec adipiscing tristique risus nec feugiat in. Aliquam id diam maecenas ultricies mi eget mauris pharetra et. Eget duis at tellus at. Facilisis mauris sit amet massa vitae tortor condimentum lacinia quis. Nunc consequat interdum varius sit amet mattis vulputate. Donec ac odio tempor orci dapibus ultrices in iaculis nunc. Sed lectus vestibulum mattis ullamcorper velit sed. Vivamus arcu felis bibendum ut tristique et. Molestie at elementum eu facilisis. Lacinia quis vel eros donec ac odio tempor orci. Curabitur vitae nunc sed velit dignissim sodales ut eu. Faucibus turpis in eu mi. Eu non diam phasellus vestibulum lorem. +Eu non diam phasellus vestibulum lorem sed risus ultricies. Et netus et malesuada fames ac turpis egestas sed. Feugiat vivamus at augue eget arcu dictum. A condimentum vitae sapien pellentesque habitant morbi tristique. Habitasse platea dictumst vestibulum rhoncus. Consectetur a erat nam at lectus urna duis. Eget nunc scelerisque viverra mauris in aliquam sem fringilla ut. Vel eros donec ac odio tempor orci. Eu consequat ac felis donec et odio. Elementum facilisis leo vel fringilla est. Ornare suspendisse sed nisi lacus sed viverra tellus. Sit amet purus gravida quis blandit turpis cursus in hac. Nunc eget lorem dolor sed. Sit amet cursus sit amet dictum sit amet justo. Quis risus sed vulputate odio ut enim blandit volutpat maecenas. Neque viverra justo nec ultrices dui sapien eget mi proin. Nam libero justo laoreet sit. +Id volutpat lacus laoreet non curabitur gravida arcu. Hendrerit dolor magna eget est lorem ipsum. Neque sodales ut etiam sit amet nisl. Gravida quis blandit turpis cursus in hac. Massa eget egestas purus viverra accumsan in nisl. In massa tempor nec feugiat. Ut diam quam nulla porttitor. Duis at consectetur lorem donec massa. Feugiat in ante metus dictum at tempor commodo. Adipiscing at in tellus integer feugiat. In pellentesque massa placerat duis ultricies lacus sed turpis tincidunt. Viverra aliquet eget sit amet tellus cras adipiscing enim. Porttitor eget dolor morbi non arcu risus. Amet venenatis urna cursus eget nunc. Tincidunt ornare massa eget egestas purus viverra accumsan in nisl. +Tempus imperdiet nulla malesuada pellentesque elit eget gravida. Dictum sit amet justo donec enim diam vulputate ut pharetra. Turpis egestas pretium aenean pharetra magna ac placerat vestibulum lectus. Velit scelerisque in dictum non consectetur a. Lacus suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. Blandit turpis cursus in hac habitasse platea dictumst quisque. Viverra adipiscing at in tellus integer feugiat scelerisque. Consectetur libero id faucibus nisl tincidunt eget nullam non. Maecenas pharetra convallis posuere morbi. Duis convallis convallis tellus id interdum velit laoreet id. Laoreet suspendisse interdum consectetur libero. Nascetur ridiculus mus mauris vitae ultricies leo integer malesuada nunc. +Est ullamcorper eget nulla facilisi etiam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Id cursus metus aliquam eleifend mi. Velit euismod in pellentesque massa placerat duis. Cursus eget nunc scelerisque viverra. Euismod in pellentesque massa placerat duis ultricies lacus sed. Velit ut tortor pretium viverra suspendisse potenti nullam ac. Diam ut venenatis tellus in metus vulputate. Malesuada fames ac turpis egestas sed tempus urna et pharetra. Non blandit massa enim nec dui nunc mattis enim ut. Vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Massa id neque aliquam vestibulum morbi blandit cursus risus. Et tortor consequat id porta. +Maecenas sed enim ut sem viverra aliquet. Diam volutpat commodo sed egestas egestas. Sit amet nisl suscipit adipiscing bibendum est. Diam sit amet nisl suscipit adipiscing. Tempus quam pellentesque nec nam aliquam. Ultrices eros in cursus turpis massa. At varius vel pharetra vel turpis nunc eget lorem dolor. Ipsum nunc aliquet bibendum enim facilisis gravida neque convallis a. Bibendum enim facilisis gravida neque convallis. Semper viverra nam libero justo laoreet sit amet. Purus in massa tempor nec feugiat nisl pretium fusce. Et netus et malesuada fames ac. Scelerisque felis imperdiet proin fermentum leo vel orci. Urna porttitor rhoncus dolor purus non. Ultricies lacus sed turpis tincidunt id aliquet risus feugiat in. Condimentum mattis pellentesque id nibh tortor id aliquet. Massa eget egestas purus viverra. Urna nunc id cursus metus. Viverra nibh cras pulvinar mattis nunc sed. +In metus vulputate eu scelerisque felis imperdiet. Amet nulla facilisi morbi tempus iaculis urna. A diam sollicitudin tempor id eu nisl nunc mi. Velit egestas dui id ornare arcu odio ut sem nulla. Diam ut venenatis tellus in metus vulputate eu scelerisque. Varius sit amet mattis vulputate enim nulla aliquet porttitor lacus. Vitae sapien pellentesque habitant morbi tristique senectus et netus. Quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus urna. Dui id ornare arcu odio. Sagittis id consectetur purus ut faucibus pulvinar. Facilisis mauris sit amet massa vitae tortor condimentum lacinia. Aliquet nibh praesent tristique magna. +Vestibulum mattis ullamcorper velit sed. Interdum velit euismod in pellentesque massa placerat duis. In mollis nunc sed id semper risus in. Pretium viverra suspendisse potenti nullam ac. Tristique risus nec feugiat in fermentum posuere urna nec. Elit pellentesque habitant morbi tristique senectus et netus et malesuada. Magna ac placerat vestibulum lectus mauris ultrices eros. Scelerisque purus semper eget duis at tellus at. Turpis egestas integer eget aliquet nibh praesent tristique magna sit. Urna id volutpat lacus laoreet non curabitur gravida arcu ac. Quam elementum pulvinar etiam non. Pretium viverra suspendisse potenti nullam ac tortor vitae purus. Arcu risus quis varius quam quisque. Ante metus dictum at tempor. Malesuada proin libero nunc consequat interdum varius sit amet mattis. Convallis a cras semper auctor. +Placerat in egestas erat imperdiet sed euismod. Volutpat maecenas volutpat blandit aliquam etiam. Morbi enim nunc faucibus a pellentesque sit amet. Egestas sed sed risus pretium quam vulputate dignissim suspendisse. Ullamcorper eget nulla facilisi etiam dignissim diam. Amet consectetur adipiscing elit pellentesque habitant morbi. Dolor magna eget est lorem ipsum. Magna ac placerat vestibulum lectus mauris ultrices eros. Suspendisse potenti nullam ac tortor vitae purus. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam erat. Netus et malesuada fames ac. Eu sem integer vitae justo eget magna fermentum iaculis eu. Sit amet est placerat in egestas erat imperdiet sed euismod. Tempor orci dapibus ultrices in iaculis nunc sed augue lacus. Amet nisl purus in mollis nunc sed id semper risus. Dictum non consectetur a erat nam. Congue quisque egestas diam in arcu cursus. Nisl nunc mi ipsum faucibus. +Rhoncus mattis rhoncus urna neque viverra justo. Risus commodo viverra maecenas accumsan. Laoreet suspendisse interdum consectetur libero id faucibus. Eget nunc lobortis mattis aliquam faucibus purus. Laoreet suspendisse interdum consectetur libero id faucibus nisl. Ultrices tincidunt arcu non sodales. Elit eget gravida cum sociis natoque penatibus et magnis dis. Pellentesque elit eget gravida cum. Mauris commodo quis imperdiet massa tincidunt. Erat velit scelerisque in dictum non. Aliquam faucibus purus in massa tempor nec feugiat nisl pretium. Viverra adipiscing at in tellus integer feugiat. Ut tristique et egestas quis ipsum suspendisse ultrices gravida dictum. Massa id neque aliquam vestibulum morbi. At in tellus integer feugiat scelerisque varius morbi. Mattis vulputate enim nulla aliquet porttitor lacus luctus accumsan tortor. Aliquam sem et tortor consequat id. Purus in mollis nunc sed id semper risus. Fringilla urna porttitor rhoncus dolor. Gravida neque convallis a cras semper auctor neque vitae. +Vitae auctor eu augue ut lectus arcu bibendum at. Non curabitur gravida arcu ac. Ultricies integer quis auctor elit sed vulputate mi sit. Fermentum dui faucibus in ornare. Sed blandit libero volutpat sed cras ornare arcu dui vivamus. Bibendum neque egestas congue quisque egestas. Non odio euismod lacinia at quis risus sed vulputate odio. Sollicitudin tempor id eu nisl nunc. Risus pretium quam vulputate dignissim suspendisse in est. Ipsum nunc aliquet bibendum enim facilisis gravida neque. Risus nec feugiat in fermentum posuere. Sed felis eget velit aliquet sagittis. Urna porttitor rhoncus dolor purus non enim. Tempus egestas sed sed risus pretium quam vulputate. Ultrices dui sapien eget mi proin sed libero. Integer feugiat scelerisque varius morbi enim nunc faucibus. +Dui accumsan sit amet nulla. Gravida cum sociis natoque penatibus et. Tristique sollicitudin nibh sit amet commodo nulla facilisi. Etiam erat velit scelerisque in dictum non. Risus nullam eget felis eget. Aliquet porttitor lacus luctus accumsan tortor posuere. Sodales neque sodales ut etiam sit amet nisl. Viverra ipsum nunc aliquet bibendum enim. Enim nec dui nunc mattis enim ut. Morbi blandit cursus risus at ultrices mi. Ac auctor augue mauris augue. Diam volutpat commodo sed egestas egestas fringilla phasellus faucibus. Semper viverra nam libero justo laoreet sit amet cursus sit. Mollis aliquam ut porttitor leo a diam. Sagittis nisl rhoncus mattis rhoncus urna neque viverra. +Purus ut faucibus pulvinar elementum integer enim neque. Egestas diam in arcu cursus euismod quis. Pulvinar mattis nunc sed blandit libero. Ultricies lacus sed turpis tincidunt id. Risus at ultrices mi tempus imperdiet nulla malesuada. Ultricies mi quis hendrerit dolor magna eget est. Nibh nisl condimentum id venenatis a condimentum vitae sapien pellentesque. Consequat ac felis donec et odio. Massa sed elementum tempus egestas sed sed. Dapibus ultrices in iaculis nunc sed augue lacus viverra vitae. Euismod in pellentesque massa placerat duis. Blandit aliquam etiam erat velit. +Interdum velit laoreet id donec ultrices tincidunt arcu non sodales. Id nibh tortor id aliquet lectus proin nibh nisl. Malesuada fames ac turpis egestas integer. Eros donec ac odio tempor orci dapibus. Urna porttitor rhoncus dolor purus non. Mi tempus imperdiet nulla malesuada pellentesque elit. Cras sed felis eget velit aliquet sagittis id consectetur. Quam id leo in vitae turpis massa sed. Elementum sagittis vitae et leo duis ut. Purus faucibus ornare suspendisse sed. Duis ut diam quam nulla porttitor massa id neque. Semper feugiat nibh sed pulvinar. Tortor consequat id porta nibh venenatis cras sed. Et ligula ullamcorper malesuada proin libero nunc consequat interdum. Cursus eget nunc scelerisque viverra mauris in aliquam sem. Morbi tempus iaculis urna id. Enim ut sem viverra aliquet eget sit. At imperdiet dui accumsan sit amet nulla. Quam id leo in vitae turpis massa sed elementum. +Egestas fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien. Vel pretium lectus quam id leo in vitae turpis massa. Ultricies leo integer malesuada nunc vel. Semper auctor neque vitae tempus quam pellentesque nec nam. Viverra maecenas accumsan lacus vel facilisis. Sit amet purus gravida quis. Est ultricies integer quis auctor elit sed. Blandit turpis cursus in hac. Pretium viverra suspendisse potenti nullam ac tortor vitae. Orci nulla pellentesque dignissim enim. Lobortis scelerisque fermentum dui faucibus in. Neque viverra justo nec ultrices dui sapien eget mi. Neque aliquam vestibulum morbi blandit cursus risus. Adipiscing elit ut aliquam purus sit amet luctus venenatis. Turpis in eu mi bibendum neque egestas congue. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla. Ipsum consequat nisl vel pretium lectus quam id leo. +Pulvinar mattis nunc sed blandit libero volutpat sed cras ornare. Laoreet id donec ultrices tincidunt arcu non sodales neque sodales. Vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Vestibulum rhoncus est pellentesque elit ullamcorper. Pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu vitae. Amet nulla facilisi morbi tempus iaculis urna. Scelerisque eu ultrices vitae auctor eu augue ut lectus. Egestas pretium aenean pharetra magna ac. Aliquet porttitor lacus luctus accumsan tortor posuere ac. Pretium nibh ipsum consequat nisl vel pretium. Vulputate odio ut enim blandit volutpat maecenas. Adipiscing elit pellentesque habitant morbi tristique senectus et netus et. +Nisi vitae suscipit tellus mauris a diam maecenas. Fusce id velit ut tortor pretium viverra. Elit pellentesque habitant morbi tristique senectus et netus et malesuada. Habitasse platea dictumst quisque sagittis. Sollicitudin tempor id eu nisl nunc. Enim facilisis gravida neque convallis a cras semper auctor neque. Dignissim diam quis enim lobortis. Dui ut ornare lectus sit. Ut venenatis tellus in metus. Id ornare arcu odio ut sem. Auctor neque vitae tempus quam pellentesque nec nam aliquam. Lobortis feugiat vivamus at augue eget arcu dictum varius duis. Arcu cursus vitae congue mauris rhoncus aenean. Orci porta non pulvinar neque laoreet suspendisse. Sed odio morbi quis commodo odio aenean sed adipiscing. +Leo vel orci porta non. Et malesuada fames ac turpis. Dictum at tempor commodo ullamcorper a lacus. Purus ut faucibus pulvinar elementum integer enim neque volutpat ac. Aliquam nulla facilisi cras fermentum. Auctor eu augue ut lectus arcu bibendum at varius vel. Pellentesque elit ullamcorper dignissim cras tincidunt lobortis. Justo laoreet sit amet cursus sit amet dictum. Nibh sit amet commodo nulla facilisi nullam vehicula ipsum a. Magna ac placerat vestibulum lectus mauris ultrices eros in cursus. Libero nunc consequat interdum varius sit. Tristique senectus et netus et malesuada fames ac turpis egestas. Tristique nulla aliquet enim tortor at auctor urna nunc. +"""; +} \ No newline at end of file diff --git a/project/FrontEnd/collaborative_science_platform/lib/utils/responsive/responsive.dart b/project/FrontEnd/collaborative_science_platform/lib/utils/responsive/responsive.dart new file mode 100644 index 00000000..40faca6b --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/utils/responsive/responsive.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +const double tabletBreakpoint = 768; +const double desktopBreakpoint = 1200; + +class Responsive extends StatelessWidget { + final Widget mobile; + final Widget? tablet; + final Widget desktop; + + const Responsive({ + Key? key, + required this.mobile, + this.tablet, + required this.desktop, + }) : super(key: key); + + static double desktopPageWidth = 1000; + + static double getGenericPageWidth(BuildContext context) { + if (isDesktop(context)) { + return desktopPageWidth; + } else { + return MediaQuery.of(context).size.width; + } + } + + static bool isMobile(BuildContext context) => MediaQuery.of(context).size.width < tabletBreakpoint; + + static bool isTablet(BuildContext context) => + MediaQuery.of(context).size.width >= tabletBreakpoint && MediaQuery.of(context).size.width < desktopBreakpoint; + + static bool isDesktop(BuildContext context) => MediaQuery.of(context).size.width >= desktopBreakpoint; + + @override + Widget build(BuildContext context) { + final double screenWidth = MediaQuery.of(context).size.width; + if (screenWidth >= desktopBreakpoint) { + return desktop; + } else if (screenWidth >= tabletBreakpoint) { + if (tablet == null) { + return desktop; + } + return tablet!; + } else { + return mobile; + } + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/utils/router.dart b/project/FrontEnd/collaborative_science_platform/lib/utils/router.dart new file mode 100644 index 00000000..a866b5c0 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/utils/router.dart @@ -0,0 +1,185 @@ +// GoRouter configuration +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/please_login_page.dart'; +import 'package:collaborative_science_platform/screens/auth_screens/signup_page.dart'; +import 'package:collaborative_science_platform/screens/graph_page/graph_page.dart'; +import 'package:collaborative_science_platform/screens/home_page/home_page.dart'; +import 'package:collaborative_science_platform/screens/node_details_page/node_details_page.dart'; +import 'package:collaborative_science_platform/screens/notifications_page/notifications_page.dart'; +import 'package:collaborative_science_platform/screens/profile_page/account_settings_page.dart'; +import 'package:collaborative_science_platform/screens/profile_page/profile_page.dart'; +import 'package:collaborative_science_platform/screens/workspace_page/workspaces_page.dart'; +import 'package:collaborative_science_platform/services/screen_navigation.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; + +import '../screens/workspace_page/create_workspace_page/mobile_create_workspace_page.dart'; + +final router = GoRouter( + navigatorKey: ScreenNavigation.navigatorKey, + initialLocation: '/', + routes: [ + GoRoute( + name: 'home', + path: HomePage.routeName, + builder: (context, state) { + Provider.of(context, listen: false).changeSelectedTab(ScreenTab.home); + return const HomePage(); + }, + ), + GoRoute( + name: LoginPage.routeName.substring(1), + path: LoginPage.routeName, + builder: (context, state) => const LoginPage(), + redirect: (context, state) { + if (context.read().isSignedIn) { + return HomePage.routeName; + } else { + return null; + } + }, + ), + GoRoute( + name: SignUpPage.routeName.substring(1), + path: SignUpPage.routeName, + builder: (context, state) => const SignUpPage(), + redirect: (context, state) { + if (context.read().isSignedIn) { + return HomePage.routeName; + } else { + return null; + } + }, + ), + GoRoute( + name: WorkspacesPage.routeName.substring(1), + path: WorkspacesPage.routeName, + builder: (context, state) { + Provider.of(context, listen: false) + .changeSelectedTab(ScreenTab.workspaces); + return const WorkspacesPage(); + }, + redirect: (context, state) { + Provider.of(context, listen: false) + .changeSelectedTab(ScreenTab.workspaces); + if (!context.read().isSignedIn) { + return '${PleaseLoginPage.routeName}${WorkspacesPage.routeName}'; + } else { + return null; + } + }, + routes: [ + GoRoute( + name: "workspace", + path: ":workspaceId", + builder: (context, state) { + Provider.of(context, listen: false) + .changeSelectedTab(ScreenTab.workspaces); + final int workspaceId = int.tryParse(state.pathParameters['workspaceId'] ?? '') ?? 0; + return WorkspacesPage(workspaceId: workspaceId); + }, + ), + ]), + GoRoute( + name: MobileCreateWorkspacePage.routeName.substring(1), + path: MobileCreateWorkspacePage.routeName, + builder: (context, state) => const MobileCreateWorkspacePage(), + ), + GoRoute( + name: GraphPage.routeName.substring(1), + path: GraphPage.routeName, + builder: (context, state) { + Provider.of(context, listen: false).changeSelectedTab(ScreenTab.graph); + return const GraphPage(); + }, + routes: [ + GoRoute( + name: "graphNode", + path: ":nodeId", + builder: (context, state) { + Provider.of(context, listen: false) + .changeSelectedTab(ScreenTab.graph); + final int nodeId = int.tryParse(state.pathParameters['nodeId'] ?? '') ?? 0; + return GraphPage(nodeId: nodeId); + }, + ), + ], + ), + GoRoute( + name: NotificationPage.routeName.substring(1), + path: NotificationPage.routeName, + builder: (context, state) { + Provider.of(context, listen: false) + .changeSelectedTab(ScreenTab.notifications); + return const NotificationPage(); + }, + redirect: (context, state) { + Provider.of(context, listen: false) + .changeSelectedTab(ScreenTab.notifications); + if (!context.read().isSignedIn) { + return '${PleaseLoginPage.routeName}${NotificationPage.routeName}'; + } else { + return null; + } + }, + ), + GoRoute( + name: AccountSettingsPage.routeName.substring(1), + path: AccountSettingsPage.routeName, + builder: (context, state) => AccountSettingsPage(), + ), + GoRoute( + name: "/please-login", + path: PleaseLoginPage.routeName, + builder: (context, state) => const PleaseLoginPage(), + ), + GoRoute( + name: PleaseLoginPage.routeName.substring(1), + path: "${PleaseLoginPage.routeName}/:pageType", + builder: (context, state) { + final String pageType = state.pathParameters['pageType'] ?? ''; + return PleaseLoginPage(pageType: pageType); + }, + ), + GoRoute( + name: NodeDetailsPage.routeName.substring(1), + path: "${NodeDetailsPage.routeName}/:nodeId", + builder: (context, state) { + Provider.of(context, listen: false).changeSelectedTab(ScreenTab.none); + final int nodeId = int.tryParse(state.pathParameters['nodeId'] ?? '') ?? 0; + return NodeDetailsPage(nodeID: nodeId); + }, + ), + GoRoute( + name: "/profile", + path: ProfilePage.routeName, + builder: (context, state) { + if (!context.read().isSignedIn) { + return PleaseLoginPage(pageType: ProfilePage.routeName.substring(1)); + } + return ProfilePage(email: ""); + }, + ), + GoRoute( + name: ProfilePage.routeName.substring(1), + path: "${ProfilePage.routeName}/:email", + builder: (context, state) { + Provider.of(context, listen: false).changeSelectedTab(ScreenTab.profile); + final String encodedEmail = state.pathParameters['email'] ?? ''; + final String email = Uri.decodeComponent(encodedEmail); + print(context.read().isSignedIn); + return ProfilePage(email: email); + }, + redirect: (context, state) { + Provider.of(context, listen: false).changeSelectedTab(ScreenTab.profile); + if (!context.read().isSignedIn && + (state.pathParameters['email'] == null || state.pathParameters['email'] == '')) { + return '${PleaseLoginPage.routeName}${ProfilePage.routeName}'; + } else { + return null; + } + }, + ), + ], +); diff --git a/project/FrontEnd/collaborative_science_platform/lib/utils/text_styles.dart b/project/FrontEnd/collaborative_science_platform/lib/utils/text_styles.dart new file mode 100644 index 00000000..8762e83b --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/utils/text_styles.dart @@ -0,0 +1,64 @@ +import 'package:collaborative_science_platform/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class TextStyles { + static const TextStyle title1 = TextStyle( + color: AppColors.primaryDarkColor, + fontSize: 50, + fontWeight: FontWeight.bold, + ); + static const TextStyle title2 = TextStyle( + color: AppColors.primaryDarkColor, + fontSize: 32, + fontWeight: FontWeight.bold, + ); + static const TextStyle title2secondary = TextStyle( + color: AppColors.secondaryDarkColor, + fontSize: 32, + fontWeight: FontWeight.bold, + ); + static const TextStyle title3 = TextStyle( + color: AppColors.primaryDarkColor, + fontSize: 24, + fontWeight: FontWeight.bold, + ); + static const TextStyle title3secondary = TextStyle( + color: AppColors.secondaryDarkColor, + fontSize: 24, + fontWeight: FontWeight.bold, + ); + static const TextStyle title4secondary = TextStyle( + color: AppColors.secondaryDarkColor, + fontSize: 20, + fontWeight: FontWeight.bold, + ); + + static const TextStyle title4 = TextStyle( + color: AppColors.primaryDarkColor, + fontSize: 16, + fontWeight: FontWeight.normal, + ); + static const TextStyle title4black = TextStyle( + fontSize: 24, + fontWeight: FontWeight.normal, + ); + + static const TextStyle bodySecondary = TextStyle( + color: AppColors.secondaryColor, + fontSize: 16, + ); + + static const TextStyle bodyBlack = TextStyle( + fontSize: 16, + ); + static const TextStyle bodyGrey = TextStyle( + fontSize: 10, + color: Colors.grey, + ); + + static const TextStyle bodyBold = TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ); +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/widgets/annotation_text.dart b/project/FrontEnd/collaborative_science_platform/lib/widgets/annotation_text.dart new file mode 100644 index 00000000..c09d1a15 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/widgets/annotation_text.dart @@ -0,0 +1,344 @@ +import 'dart:ui'; + +import 'package:collaborative_science_platform/utils/responsive/responsive.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; + +class AnnotationText extends StatelessWidget { + final String text; + final TextStyle? style; + final TextAlign? textAlign; + final int? maxLines; + + const AnnotationText(this.text, {super.key, this.style, this.textAlign, this.maxLines}); + + @override + Widget build(BuildContext context) { + return SelectableText( + text, + style: style, + maxLines: maxLines, + showCursor: true, + textAlign: textAlign, + contextMenuBuilder: (context, editableTextState) { + String selectedText = editableTextState.textEditingValue.selection.textInside(text); + return _MyContextMenu( + anchor: editableTextState.contextMenuAnchors.primaryAnchor, + selectedText: selectedText.trim(), + children: AdaptiveTextSelectionToolbar.getAdaptiveButtons( + context, + editableTextState.contextMenuButtonItems, + ).toList(), + ); + }, + ); + } +} + +class _MyContextMenu extends StatelessWidget { + const _MyContextMenu({ + required this.anchor, + required this.children, + required this.selectedText, + }); + + final Offset anchor; + final List children; + final String selectedText; + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned( + top: anchor.dy, + left: anchor.dx, + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.0), + color: Colors.grey[900]!.withOpacity(0.7)), + width: 180, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + //...children, + + AddAnnotationButton(text: selectedText), + const SizedBox(height: 2), + ShowAnnotationButton(text: selectedText), + ], + ), + ), + ), + ], + ); + } +} + +class ShowAnnotationButton extends StatelessWidget { + final String text; + const ShowAnnotationButton({super.key, required this.text}); + + @override + Widget build(BuildContext context) { + return Responsive( + mobile: MobileShowAnnotationButton(text), desktop: DesktopShowAnnotationButton(text)); + } +} + +class MobileShowAnnotationButton extends StatefulWidget { + final String text; + const MobileShowAnnotationButton(this.text, {super.key}); + + @override + State createState() => _MobileShowAnnotationButtonState(); +} + +class _MobileShowAnnotationButtonState extends State { + bool isHovering = false; + bool isPortalOpen = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (event) => setState(() { + isHovering = true; + isPortalOpen = true; + }), + onExit: (event) => setState(() { + isHovering = false; + isPortalOpen = false; + }), + child: GestureDetector( + // Show Popup on Tap + onTap: () => showDialog( + context: context, + builder: (BuildContext context) { + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: AlertDialog( + backgroundColor: Colors.grey[800]!.withOpacity(0.7), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), + elevation: 20, + title: Text( + widget.text, + style: const TextStyle( + color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), + ), + content: const SizedBox( + height: 200, + width: 400, + child: Column( + children: [ + SelectableText( + "Automaton is a relatively self-operating machine.", + style: TextStyle(color: Colors.white), + maxLines: 5, + ) + ], + ), + ), + ), + ); + }, + ), + child: AnnotationButtonItem(isHovering: isHovering, text: "Show Annotation"), + ), + ); + } +} + +class DesktopShowAnnotationButton extends StatefulWidget { + final String text; + const DesktopShowAnnotationButton(this.text, {super.key}); + + @override + State createState() => _DesktopShowAnnotationButtonState(); +} + +class _DesktopShowAnnotationButtonState extends State { + bool isHovering = false; + bool isPortalOpen = false; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (event) => setState(() { + isHovering = true; + isPortalOpen = true; + }), + onExit: (event) => setState(() { + isHovering = false; + isPortalOpen = false; + }), + child: GestureDetector( + onTap: () => setState(() { + isHovering = true; + isPortalOpen = true; + }), + child: PortalTarget( + visible: isPortalOpen, + fit: StackFit.passthrough, + anchor: Responsive.isMobile(context) + ? const Aligned( + follower: Alignment.topLeft, + target: Alignment.bottomLeft, + ) + : const Aligned( + follower: Alignment.topLeft, + target: Alignment.topRight, + backup: Aligned( + follower: Alignment.topRight, + target: Alignment.topLeft, + backup: Aligned( + follower: Alignment.bottomRight, + target: Alignment.bottomLeft, + ))), + portalFollower: MouseRegion( + onHover: (event) => setState(() { + isPortalOpen = true; + }), + onEnter: (event) => setState(() { + isPortalOpen = true; + }), + onExit: (event) => setState(() { + isPortalOpen = false; + }), + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5.0), + color: Colors.grey[900]!.withOpacity(0.9)), + width: 400, + constraints: const BoxConstraints(maxWidth: 400), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Text(widget.text, + style: const TextStyle(fontSize: 16, color: Colors.white)), + ], + ), + const SizedBox(height: 3), + const SelectableText("Automaton is a relatively self-operating machine.", + style: TextStyle(fontSize: 12, color: Colors.white)), + ], + ), + ), + ), + ), + child: AnnotationButtonItem(isHovering: isHovering, text: "Show Annotation"), + ), + ), + ); + } +} + +class AddAnnotationButton extends StatefulWidget { + final String text; + const AddAnnotationButton({super.key, required this.text}); + + @override + State createState() => _AddAnnotationButtonState(); +} + +class _AddAnnotationButtonState extends State { + bool isHovering = false; + bool isPortalOpen = false; + final TextEditingController _textEditingController = TextEditingController(); + + void _submit() { + print(_textEditingController.text); + ContextMenuController.removeAny(); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (event) => setState(() { + isHovering = true; + isPortalOpen = true; + }), + onExit: (event) => setState(() { + isHovering = false; + isPortalOpen = false; + }), + child: GestureDetector( + // Show Popup on Tap + onTap: () => showDialog( + context: context, + builder: (BuildContext context) { + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: AlertDialog( + backgroundColor: Colors.grey[800]!.withOpacity(0.7), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), + elevation: 20, + title: Text( + widget.text, + style: const TextStyle( + color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), + ), + content: SizedBox( + height: 200, + width: 400, + child: Column(children: [ + TextField( + controller: _textEditingController, + style: const TextStyle(color: Colors.white), + decoration: InputDecoration( + border: OutlineInputBorder(borderRadius: BorderRadius.circular(6)), + labelText: 'Annotation', + labelStyle: TextStyle(color: Colors.grey[500]), + ), + maxLines: 5, + ) + ])), + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + actions: [ + TextButton( + onPressed: () { + _submit(); + Navigator.of(context).pop(); + }, + child: const Text('Save', style: TextStyle(color: Colors.white)), + ), + ], + ), + ); + }, + ), + child: AnnotationButtonItem(isHovering: isHovering, text: "Add Annotation"), + ), + ); + } +} + +class AnnotationButtonItem extends StatelessWidget { + final bool isHovering; + final String text; + const AnnotationButtonItem({super.key, required this.isHovering, required this.text}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: isHovering ? Colors.blue[700] : Colors.transparent, + ), + child: Text( + text, + style: const TextStyle(color: Colors.white, fontSize: 12), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/widgets/app_button.dart b/project/FrontEnd/collaborative_science_platform/lib/widgets/app_button.dart new file mode 100644 index 00000000..53ecd144 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/widgets/app_button.dart @@ -0,0 +1,66 @@ +import 'package:collaborative_science_platform/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class AppButton extends StatelessWidget { + final String text; + final double height; + final void Function() onTap; + final bool isActive; + final bool isLoading; + final Widget? icon; + final String type; + + const AppButton({ + super.key, + required this.text, + required this.height, + required this.onTap, + this.isActive = true, + this.isLoading = false, + this.icon, + this.type = "primary", + }); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: isActive ? onTap : () {}, + style: type != "outlined" + ? ElevatedButton.styleFrom( + backgroundColor: isActive + ? (type == "primary" + ? AppColors.primaryColor + : (type == "secondary" ? AppColors.secondaryColor : Colors.grey[600])) + : Colors.grey[600], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + minimumSize: Size(double.infinity, height), + ) + : ElevatedButton.styleFrom( + backgroundColor: isActive ? Colors.white : Colors.grey[600], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + minimumSize: Size(double.infinity, height), + side: const BorderSide(color: AppColors.primaryColor)), + child: isLoading + ? const Center( + child: CircularProgressIndicator(), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) icon!, + if (icon != null) const SizedBox(width: 6), + Text(text, + style: TextStyle( + fontSize: height / 3.0, + fontWeight: FontWeight.bold, + color: (type != "outlined" ? Colors.white : AppColors.primaryColor), + )), + ], + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/widgets/app_search_bar.dart b/project/FrontEnd/collaborative_science_platform/lib/widgets/app_search_bar.dart new file mode 100644 index 00000000..66eac6d7 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/widgets/app_search_bar.dart @@ -0,0 +1,184 @@ +import 'package:collaborative_science_platform/helpers/search_helper.dart'; +import 'package:collaborative_science_platform/utils/responsive/responsive.dart'; +import 'package:flutter/material.dart'; + +enum SearchType { theorem, author, by, both } + +enum SearchOption { semantic, exact } + +class AppSearchBar extends StatefulWidget { + final Function onSearch; + final FocusNode focusNode; + final bool hideSearchType; + final String hintText; + + const AppSearchBar( + {required this.onSearch, + required this.focusNode, + this.hideSearchType = false, + this.hintText = "Search", + super.key}); + + @override + State createState() => _AppSearchBarState(); +} + +class _AppSearchBarState extends State { + SearchType searchType = SearchHelper.searchType; + final TextEditingController _controller = TextEditingController(); + + Widget searchTypeSelector() { + if (Responsive.isMobile(context)) { + return Icon( + (searchType == SearchType.theorem) + ? Icons.description_rounded + : (searchType == SearchType.author) + ? Icons.person + : (searchType == SearchType.by) + ? Icons.person_2_outlined + : Icons.list_rounded, + color: Colors.indigo.shade500, + ); + } else { + return Row( + children: [ + Icon( + (searchType == SearchType.theorem) + ? Icons.description_rounded + : (searchType == SearchType.author) + ? Icons.person + : (searchType == SearchType.by) + ? Icons.person_2_outlined + : Icons.list_rounded, + color: Colors.indigo.shade500, + ), + const SizedBox(width: 4.0), + Text( + (searchType == SearchType.theorem) + ? "Theorem" + : (searchType == SearchType.author) + ? "Author" + : (searchType == SearchType.by) + ? "By" + : "Both", + style: TextStyle( + color: Colors.indigo.shade500, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + } + + @override + Widget build(BuildContext context) { + return Container( + height: 38, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[400]!), + ), + child: Row( + children: [ + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => widget.onSearch(_controller.text), + child: Container( + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + bottomLeft: Radius.circular(8), + ), + ), + width: 38, + height: 38, + child: Icon( + Icons.search, + color: Colors.indigo[500], + size: 24, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextField( + textAlignVertical: TextAlignVertical.center, + controller: _controller, + focusNode: widget.focusNode, + textInputAction: TextInputAction.search, + onSubmitted: (String value) => widget.onSearch(value), + decoration: InputDecoration( + hintText: widget.hintText, + border: InputBorder.none, + hintStyle: TextStyle(color: Colors.grey[600]!), + isCollapsed: true, + ), + ), + ), + const SizedBox(width: 4.0), + if (!widget.hideSearchType) + PopupMenuButton( + position: PopupMenuPosition.under, + color: Colors.grey.shade200, + onSelected: (SearchType newSearchType) { + setState(() { + searchType = newSearchType; + SearchHelper.searchType = newSearchType; + }); + }, + initialValue: searchType, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: SearchType.theorem, + child: Row( + children: [ + Icon(Icons.description_rounded), + SizedBox(width: 4.0), + Text("Theorem"), + ], + ), + ), + const PopupMenuItem( + value: SearchType.author, + child: Row( + children: [ + Icon(Icons.person), + SizedBox(width: 4.0), + Text("Author"), + ], + ), + ), + const PopupMenuItem( + value: SearchType.by, + child: Row( + children: [ + Icon(Icons.person_2_outlined), + SizedBox(width: 4.0), + Text("By"), + ], + ), + ), + const PopupMenuItem( + value: SearchType.both, + child: Row( + children: [ + Icon(Icons.list_rounded), + SizedBox(width: 4.0), + Text("Both"), + ], + ), + ) + ], + child: searchTypeSelector(), + ), + SizedBox(width: (Responsive.isMobile(context)) ? 10.0 : 20.0), + ], + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/widgets/app_text_field.dart b/project/FrontEnd/collaborative_science_platform/lib/widgets/app_text_field.dart new file mode 100644 index 00000000..df25eb59 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/widgets/app_text_field.dart @@ -0,0 +1,66 @@ +import 'package:collaborative_science_platform/utils/colors.dart'; +import 'package:flutter/material.dart'; + +class AppTextField extends StatelessWidget { + final TextEditingController controller; + final FocusNode focusNode; + final String hintText; + final bool obscureText; + final double height; + final Color color; + final Widget? prefixIcon; + final Widget? suffixIcon; + final void Function(String)? onChanged; + final int maxLines; + final TextInputType? textInputType; + + const AppTextField({ + super.key, + required this.controller, + required this.focusNode, + required this.hintText, + required this.obscureText, + required this.height, + this.color = AppColors.primaryColor, + this.prefixIcon, + this.suffixIcon, + this.onChanged, + this.maxLines = 1, + this.textInputType, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: height, + child: TextField( + maxLines: maxLines, + controller: controller, + focusNode: focusNode, + obscureText: obscureText, + onChanged: onChanged, + keyboardType: textInputType, + cursorColor: Colors.grey.shade700, + style: const TextStyle(color: Colors.black), + decoration: InputDecoration( + prefixIconColor: Colors.grey, + prefixIcon: prefixIcon, + suffixIconColor: Colors.grey, + suffixIcon: suffixIcon, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: color), + borderRadius: BorderRadius.circular(10.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.secondaryDarkColor), + borderRadius: BorderRadius.circular(10.0), + ), + fillColor: Colors.white, + filled: true, + hintText: hintText, + hintStyle: const TextStyle(color: Colors.grey), + ), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/widgets/card_container.dart b/project/FrontEnd/collaborative_science_platform/lib/widgets/card_container.dart new file mode 100644 index 00000000..e60960f2 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/widgets/card_container.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; + +class CardContainer extends StatelessWidget { + final Widget child; + final Function? onTap; + const CardContainer({super.key, required this.child, this.onTap}); + + @override + Widget build(BuildContext context) { + if (onTap == null) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + blurRadius: 7, + offset: const Offset(0, 2), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ); + } else { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + onTap!(); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + blurRadius: 7, + offset: const Offset(0, 2), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ), + ); + } + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/widgets/search_bar_extended.dart b/project/FrontEnd/collaborative_science_platform/lib/widgets/search_bar_extended.dart new file mode 100644 index 00000000..9f68a7aa --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/widgets/search_bar_extended.dart @@ -0,0 +1,246 @@ +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/widgets/app_search_bar.dart'; +import 'package:easy_search_bar/easy_search_bar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SearchBarExtended extends StatefulWidget { + final Function semanticSearch; + final Function exactSearch; + const SearchBarExtended({super.key, required this.semanticSearch, required this.exactSearch}); + + @override + State createState() => _SearchBarExtendedState(); +} + +class _SearchBarExtendedState extends State { + SearchType searchType = SearchHelper.searchType; + SearchOption searchOption = SearchHelper.searchOption; + final List semantics = []; + + Widget searchTypeSelector() { + // if (Responsive.isMobile(context)) { + // return Icon( + // (searchType == SearchType.theorem) + // ? Icons.description_rounded + // : (searchType == SearchType.author) + // ? Icons.person + // : (searchType == SearchType.by) + // ? Icons.person_2_outlined + // : Icons.list_rounded, + // color: Colors.indigo.shade500, + // ); + + return Row( + children: [ + Icon( + (searchType == SearchType.theorem) + ? Icons.description_rounded + : (searchType == SearchType.author) + ? Icons.person + : (searchType == SearchType.by) + ? Icons.person_2_outlined + : Icons.list_rounded, + color: Colors.indigo.shade500, + ), + const SizedBox(width: 4.0), + Text( + (searchType == SearchType.theorem) + ? "Theorem" + : (searchType == SearchType.author) + ? "Author" + : (searchType == SearchType.by) + ? "By" + : "Both", + style: TextStyle( + color: Colors.indigo.shade500, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + + Widget searchTypeSelector2() { + // if (Responsive.isMobile(context)) { + // return Icon( + // (searchOption == SearchOption.semantic) ? Icons.abc : CupertinoIcons.smallcircle_circle, + // color: Colors.indigo.shade500, + // ); + + return Row( + children: [ + Icon( + (searchOption == SearchOption.semantic) ? Icons.abc : CupertinoIcons.smallcircle_circle, + color: Colors.indigo.shade500, + ), + const SizedBox(width: 4.0), + Text( + (searchOption == SearchOption.semantic) ? "Semantic" : "Exact", + style: TextStyle( + color: Colors.indigo.shade500, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + + Future> _getSuggestions(String query) async { + if (searchOption == SearchOption.exact) return []; + semantics.clear(); + if (query.length < 3) return []; + final NodeProvider nodeProvider = Provider.of(context, listen: false); + try { + await nodeProvider.semanticSuggestions(query); + } on SearchError { + return []; + } + semantics.addAll(nodeProvider.semanticTags.map((e) => e.label)); + return semantics; + } + + SemanticTag getTag(String label) { + final NodeProvider nodeProvider = Provider.of(context, listen: false); + return nodeProvider.semanticTags.firstWhere((element) => element.label == label); + } + + Widget _suggestionLoaderBuilder() { + if (searchOption == SearchOption.exact) return const SizedBox(); + return const Center( + child: CircularProgressIndicator(), + ); + } + + @override + Widget build(BuildContext context) { + var actions2 = Row( + children: [ + const SizedBox(width: 8.0), + PopupMenuButton( + position: PopupMenuPosition.under, + color: Colors.grey.shade200, + onSelected: (SearchOption newSearchType) { + setState(() { + searchOption = newSearchType; + SearchHelper.searchOption = newSearchType; + }); + }, + initialValue: searchOption, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: SearchOption.semantic, + child: Row( + children: [ + Icon(Icons.abc), + SizedBox(width: 8.0), + Text("Semantic"), + ], + ), + ), + const PopupMenuItem( + value: SearchOption.exact, + child: Row( + children: [ + Icon(CupertinoIcons.smallcircle_circle), + SizedBox(width: 4.0), + Text("Exact"), + ], + ), + ), + ], + child: searchTypeSelector2(), + ), + const SizedBox(width: 6.0), + if (searchOption == SearchOption.exact) + PopupMenuButton( + position: PopupMenuPosition.under, + color: Colors.grey.shade200, + onSelected: (SearchType newSearchType) { + setState(() { + searchType = newSearchType; + SearchHelper.searchType = newSearchType; + }); + }, + initialValue: searchType, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: SearchType.theorem, + child: Row( + children: [ + Icon(Icons.description_rounded), + SizedBox(width: 4.0), + Text("Theorem"), + ], + ), + ), + const PopupMenuItem( + value: SearchType.author, + child: Row( + children: [ + Icon(Icons.person), + SizedBox(width: 4.0), + Text("User"), + ], + ), + ), + const PopupMenuItem( + value: SearchType.by, + child: Row( + children: [ + Icon(Icons.person_2_outlined), + SizedBox(width: 4.0), + Text("Author"), + ], + ), + ), + const PopupMenuItem( + value: SearchType.both, + child: Row( + children: [ + Icon(Icons.list_rounded), + SizedBox(width: 4.0), + Text("Both"), + ], + ), + ) + ], + child: searchTypeSelector(), + ), + ], + ); + + return SizedBox( + height: 55, + child: EasySearchBar( + title: const SizedBox(), + onSearch: (value) { + if (searchOption == SearchOption.exact) { + widget.exactSearch(value); + } + }, + leading: actions2, + asyncSuggestions: + (searchOption == SearchOption.exact) ? null : (value) => _getSuggestions(value), + suggestionLoaderBuilder: () => _suggestionLoaderBuilder(), + suggestionBuilder: (data) { + final SemanticTag tag = getTag(data); + return ListTile( + title: Text(tag.label), + subtitle: Text(tag.description), + ); + }, + onSuggestionTap: (data) => widget.semanticSearch(getTag(data)), + searchTextStyle: const TextStyle(color: Colors.grey), + elevation: 5, + searchBackIconTheme: const IconThemeData(color: Colors.grey), + backgroundColor: Colors.white, + titleTextStyle: TextStyle(color: Colors.grey[600]!), + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/lib/widgets/simple_app_bar.dart b/project/FrontEnd/collaborative_science_platform/lib/widgets/simple_app_bar.dart new file mode 100644 index 00000000..fcd78f5a --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/lib/widgets/simple_app_bar.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class SimpleAppBar extends StatelessWidget { + final String title; + const SimpleAppBar({ + super.key, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.arrow_back_rounded) + ), + Text( + title, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, + ) + ), + ], + ), + ); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/linux/.gitignore b/project/FrontEnd/collaborative_science_platform/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/project/FrontEnd/collaborative_science_platform/linux/CMakeLists.txt b/project/FrontEnd/collaborative_science_platform/linux/CMakeLists.txt new file mode 100644 index 00000000..9683af05 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "collaborative_science_platform") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.collaborative_science_platform") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/project/FrontEnd/collaborative_science_platform/linux/flutter/CMakeLists.txt b/project/FrontEnd/collaborative_science_platform/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugin_registrant.cc b/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..f6f23bfe --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugin_registrant.h b/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugins.cmake b/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..f16b4c34 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/project/FrontEnd/collaborative_science_platform/linux/main.cc b/project/FrontEnd/collaborative_science_platform/linux/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/project/FrontEnd/collaborative_science_platform/linux/my_application.cc b/project/FrontEnd/collaborative_science_platform/linux/my_application.cc new file mode 100644 index 00000000..c655ad11 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "collaborative_science_platform"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "collaborative_science_platform"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/project/FrontEnd/collaborative_science_platform/linux/my_application.h b/project/FrontEnd/collaborative_science_platform/linux/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/project/FrontEnd/collaborative_science_platform/macos/.gitignore b/project/FrontEnd/collaborative_science_platform/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/project/FrontEnd/collaborative_science_platform/macos/Flutter/Flutter-Debug.xcconfig b/project/FrontEnd/collaborative_science_platform/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/project/FrontEnd/collaborative_science_platform/macos/Flutter/Flutter-Release.xcconfig b/project/FrontEnd/collaborative_science_platform/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/project/FrontEnd/collaborative_science_platform/macos/Flutter/GeneratedPluginRegistrant.swift b/project/FrontEnd/collaborative_science_platform/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..169c3fdc --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_foundation +import share_plus +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/project/FrontEnd/collaborative_science_platform/macos/Podfile b/project/FrontEnd/collaborative_science_platform/macos/Podfile new file mode 100644 index 00000000..c795730d --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# 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', 'ephemeral', '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 Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_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_macos_build_settings(target) + end +end diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/project.pbxproj b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..233332b5 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* collaborative_science_platform.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "collaborative_science_platform.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* collaborative_science_platform.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* collaborative_science_platform.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + 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)/collaborative_science_platform.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/collaborative_science_platform"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + 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)/collaborative_science_platform.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/collaborative_science_platform"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + 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)/collaborative_science_platform.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/collaborative_science_platform"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..ea6c4080 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner.xcworkspace/contents.xcworkspacedata b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/AppDelegate.swift b/project/FrontEnd/collaborative_science_platform/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..d53ef643 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Base.lproj/MainMenu.xib b/project/FrontEnd/collaborative_science_platform/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/AppInfo.xcconfig b/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..c4f41796 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = collaborative_science_platform + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.collaborativeSciencePlatform + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Debug.xcconfig b/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Release.xcconfig b/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Warnings.xcconfig b/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/DebugProfile.entitlements b/project/FrontEnd/collaborative_science_platform/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Info.plist b/project/FrontEnd/collaborative_science_platform/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/MainFlutterWindow.swift b/project/FrontEnd/collaborative_science_platform/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/project/FrontEnd/collaborative_science_platform/macos/Runner/Release.entitlements b/project/FrontEnd/collaborative_science_platform/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/project/FrontEnd/collaborative_science_platform/macos/RunnerTests/RunnerTests.swift b/project/FrontEnd/collaborative_science_platform/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..5418c9f5 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +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/pubspec.lock b/project/FrontEnd/collaborative_science_platform/pubspec.lock new file mode 100644 index 00000000..0e566143 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/pubspec.lock @@ -0,0 +1,674 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42" + url: "https://pub.dev" + source: hosted + version: "4.2.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + url: "https://pub.dev" + source: hosted + version: "1.17.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" + url: "https://pub.dev" + source: hosted + version: "0.3.3+6" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + easy_search_bar: + dependency: "direct main" + description: + name: easy_search_bar + sha256: b20343be8ab94ed9e1550cf8cbd5323c344bd9b58b9319fb6e62d75395c0bcaa + url: "https://pub.dev" + source: hosted + version: "2.5.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + float_column: + dependency: transitive + description: + name: float_column + sha256: "518b9d49ca8c9edbd35011325913ec6a6f7835fa665fd6e4cfdc48fc7d7de4eb" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_portal: + dependency: "direct main" + description: + name: flutter_portal + sha256: "4601b3dc24f385b3761721bd852a3f6c09cddd4e943dd184ed58ee1f43006257" + url: "https://pub.dev" + source: hosted + version: "1.1.4" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_tex: + dependency: "direct main" + description: + name: flutter_tex + sha256: "8fa2079e8b338da05ceada989826bcf69ab3a9c767d710c21362dad16eca4166" + url: "https://pub.dev" + source: hosted + version: "4.0.3+4" + flutter_web_plugins: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: "00d1b67d6e9fa443331da229084dd3eb04407f5a2dff22940bd7bba6af5722c3" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + http: + dependency: "direct main" + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + just_the_tooltip: + dependency: "direct main" + description: + name: just_the_tooltip + sha256: "7a081133d57285bfb41b331f411006d57b433d7b35772e6155745f6a7a09cb82" + url: "https://pub.dev" + source: hosted + version: "0.0.12" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: "01512006c8429f604eb10f9848717baeaedf99e991d14a50d540d9beff08e5c6" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + material_floating_search_bar_2: + dependency: "direct main" + description: + name: material_floating_search_bar_2 + sha256: ab0c6d209d9491f98dd4c72f2641d0ba1dd35c87effca1f23d8679bece43add0 + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + platform: + dependency: transitive + description: + name: platform + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + url: "https://pub.dev" + source: hosted + version: "2.1.6" + provider: + dependency: "direct main" + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" + selectable: + dependency: "direct main" + description: + name: selectable + sha256: "18432ba915b3e82a367e6ec9038666e0cc3147336cba125a9aa5768dc84296eb" + url: "https://pub.dev" + source: hosted + version: "0.3.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd + url: "https://pub.dev" + source: hosted + version: "7.2.1" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 + url: "https://pub.dev" + source: hosted + version: "3.3.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba + url: "https://pub.dev" + source: hosted + version: "6.2.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: b16dadf7eb610e20da044c141b4a0199a5e8082ca21daba68322756f953ce714 + url: "https://pub.dev" + source: hosted + version: "1.1.9" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: a4b01403d5c613db115e30e71eca33f7e9e09f2d3c52c3fb84e16333ecddc539 + url: "https://pub.dev" + source: hosted + version: "1.1.9" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d26c0e2f237476426523eb25512e4c09fa27c6d33ed659a0e69d79e20b5dc47f + url: "https://pub.dev" + source: hosted + version: "1.1.9" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: "392c1d83b70fe2495de3ea2c84531268d5b8de2de3f01086a53334d8b6030a88" + url: "https://pub.dev" + source: hosted + version: "3.0.4" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "8b3b2450e98876c70bfcead876d9390573b34b9418c19e28168b74f6cb252dbd" + url: "https://pub.dev" + source: hosted + version: "2.10.4" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "812165e4e34ca677bdfbfa58c01e33b27fd03ab5fa75b70832d4b7d4ca1fa8cf" + url: "https://pub.dev" + source: hosted + version: "1.9.5" + webview_flutter_plus: + dependency: transitive + description: + name: webview_flutter_plus + sha256: bea8756ae096529254725def7c4a633851a785c7d49206e0817125ab02b14307 + url: "https://pub.dev" + source: hosted + version: "0.3.0+2" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: a5364369c758892aa487cbf59ea41d9edd10f9d9baf06a94e80f1bd1b4c7bbc0 + url: "https://pub.dev" + source: hosted + version: "2.9.5" + win32: + dependency: transitive + description: + name: win32 + sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + xml: + dependency: transitive + description: + name: xml + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + url: "https://pub.dev" + source: hosted + version: "6.3.0" +sdks: + dart: ">=3.1.3 <4.0.0" + flutter: ">=3.13.0" diff --git a/project/FrontEnd/collaborative_science_platform/pubspec.yaml b/project/FrontEnd/collaborative_science_platform/pubspec.yaml new file mode 100644 index 00000000..4b210b89 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/pubspec.yaml @@ -0,0 +1,103 @@ +name: collaborative_science_platform +description: Collaborative Science Platform Application. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ">=3.1.3 <4.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.6 + flutter_svg: ^2.0.7 + http: ^1.1.0 + provider: ^6.0.5 + carousel_slider: ^4.2.1 + + go_router: ^7.1.1 + intl: ^0.18.1 + selectable: ^0.3.0 + flutter_portal: ^1.1.4 + just_the_tooltip: ^0.0.12 + flutter_tex: ^4.0.3+4 + easy_search_bar: ^2.5.0 + share_plus: ^7.2.1 + material_floating_search_bar_2: ^0.5.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/images/ + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/project/FrontEnd/collaborative_science_platform/run.sh b/project/FrontEnd/collaborative_science_platform/run.sh new file mode 100755 index 00000000..3c3c1e3e --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/run.sh @@ -0,0 +1 @@ +flutter run -d chrome --web-renderer html --web-port 8080 \ No newline at end of file diff --git a/project/FrontEnd/collaborative_science_platform/test/widget_test.dart b/project/FrontEnd/collaborative_science_platform/test/widget_test.dart new file mode 100644 index 00000000..b80f5569 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:collaborative_science_platform/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/project/FrontEnd/collaborative_science_platform/web/favicon.png b/project/FrontEnd/collaborative_science_platform/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/web/favicon.png differ diff --git a/project/FrontEnd/collaborative_science_platform/web/icons/Icon-192.png b/project/FrontEnd/collaborative_science_platform/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/web/icons/Icon-192.png differ diff --git a/project/FrontEnd/collaborative_science_platform/web/icons/Icon-512.png b/project/FrontEnd/collaborative_science_platform/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/web/icons/Icon-512.png differ diff --git a/project/FrontEnd/collaborative_science_platform/web/icons/Icon-maskable-192.png b/project/FrontEnd/collaborative_science_platform/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/web/icons/Icon-maskable-192.png differ diff --git a/project/FrontEnd/collaborative_science_platform/web/icons/Icon-maskable-512.png b/project/FrontEnd/collaborative_science_platform/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/web/icons/Icon-maskable-512.png differ diff --git a/project/FrontEnd/collaborative_science_platform/web/index.html b/project/FrontEnd/collaborative_science_platform/web/index.html new file mode 100644 index 00000000..62a5566b --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/web/index.html @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + collaborative_science_platform + + + + + + + + + + + + + diff --git a/project/FrontEnd/collaborative_science_platform/web/manifest.json b/project/FrontEnd/collaborative_science_platform/web/manifest.json new file mode 100644 index 00000000..aae26ae8 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "collaborative_science_platform", + "short_name": "collaborative_science_platform", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/project/FrontEnd/collaborative_science_platform/windows/.gitignore b/project/FrontEnd/collaborative_science_platform/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/project/FrontEnd/collaborative_science_platform/windows/CMakeLists.txt b/project/FrontEnd/collaborative_science_platform/windows/CMakeLists.txt new file mode 100644 index 00000000..b9a9f671 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(collaborative_science_platform LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "collaborative_science_platform") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/project/FrontEnd/collaborative_science_platform/windows/flutter/CMakeLists.txt b/project/FrontEnd/collaborative_science_platform/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..930d2071 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugin_registrant.cc b/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..c3384ec5 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugin_registrant.h b/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugins.cmake b/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..01d38362 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + share_plus + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/CMakeLists.txt b/project/FrontEnd/collaborative_science_platform/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/Runner.rc b/project/FrontEnd/collaborative_science_platform/windows/runner/Runner.rc new file mode 100644 index 00000000..6997b7dc --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "collaborative_science_platform" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "collaborative_science_platform" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "collaborative_science_platform.exe" "\0" + VALUE "ProductName", "collaborative_science_platform" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/flutter_window.cpp b/project/FrontEnd/collaborative_science_platform/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/flutter_window.h b/project/FrontEnd/collaborative_science_platform/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/main.cpp b/project/FrontEnd/collaborative_science_platform/windows/runner/main.cpp new file mode 100644 index 00000000..10133ffe --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"collaborative_science_platform", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/resource.h b/project/FrontEnd/collaborative_science_platform/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/resources/app_icon.ico b/project/FrontEnd/collaborative_science_platform/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/project/FrontEnd/collaborative_science_platform/windows/runner/resources/app_icon.ico differ diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/runner.exe.manifest b/project/FrontEnd/collaborative_science_platform/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..a42ea768 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/utils.cpp b/project/FrontEnd/collaborative_science_platform/windows/runner/utils.cpp new file mode 100644 index 00000000..b2b08734 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/utils.h b/project/FrontEnd/collaborative_science_platform/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/win32_window.cpp b/project/FrontEnd/collaborative_science_platform/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/project/FrontEnd/collaborative_science_platform/windows/runner/win32_window.h b/project/FrontEnd/collaborative_science_platform/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/project/FrontEnd/collaborative_science_platform/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_