From 90585fdc062022b1aadef29e9b2356ae8b8c2cbb Mon Sep 17 00:00:00 2001 From: Omer Date: Mon, 27 Nov 2023 22:14:52 +0300 Subject: [PATCH] Add frontend files --- project/FrontEnd/.idea/workspace.xml | 48 ++ .../collaborative_science_platform/.gitignore | 44 ++ .../collaborative_science_platform/.metadata | 45 ++ .../collaborative_science_platform/Dockerfile | 33 + .../collaborative_science_platform/README.md | 55 ++ .../analysis_options.yaml | 28 + .../android/.gitignore | 13 + .../android/app/build.gradle | 67 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 39 + .../MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../android/build.gradle | 31 + .../android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + .../android/settings.gradle | 20 + .../assets/images/.gitkeep | 0 .../assets/images/gumball.jpg | Bin 0 -> 72114 bytes .../assets/images/logo.svg | 10 + .../assets/images/logo_small.svg | 9 + .../ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../ios/Flutter/Debug.xcconfig | 2 + .../ios/Flutter/Release.xcconfig | 2 + .../ios/Podfile | 44 ++ .../ios/Podfile.lock | 41 + .../ios/Runner.xcodeproj/project.pbxproj | 722 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../ios/Runner/Info.plist | 54 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../ios/RunnerTests/RunnerTests.swift | 12 + .../lib/data/.gitkeep | 0 .../lib/exceptions/auth_exceptions.dart | 14 + .../exceptions/node_details_exceptions.dart | 16 + .../exceptions/profile_page_exceptions.dart | 4 + .../lib/exceptions/search_exceptions.dart | 9 + .../lib/exceptions/workspace_exceptions.dart | 56 ++ .../lib/extensions/.gitkeep | 0 .../lib/helpers/date_to_string.dart | 48 ++ .../lib/helpers/node_helper.dart | 20 + .../lib/helpers/search_helper.dart | 6 + .../lib/main.dart | 80 ++ .../lib/models/account.dart | 26 + .../lib/models/annotation.dart | 25 + .../lib/models/base_user.dart | 11 + .../lib/models/basic_user.dart | 22 + .../lib/models/collaboration_request.dart | 23 + .../lib/models/contributor_user.dart | 11 + .../lib/models/node.dart | 39 + .../node_details_page/node_detailed.dart | 80 ++ .../lib/models/node_details_page/proof.dart | 28 + .../models/node_details_page/question.dart | 31 + .../lib/models/notification.dart | 22 + .../lib/models/profile_data.dart | 77 ++ .../lib/models/proof.dart | 17 + .../lib/models/question.dart | 20 + .../lib/models/request.dart | 18 + .../lib/models/review.dart | 19 + .../lib/models/review_request.dart | 28 + .../lib/models/reviewer.dart | 34 + .../lib/models/semantic_tag.dart | 19 + .../lib/models/status.dart | 5 + .../lib/models/tag.dart | 13 + .../lib/models/theorem.dart | 16 + .../lib/models/user.dart | 31 + .../lib/models/wiki_tag.dart | 6 + .../lib/models/workspaces_page/entry.dart | 42 + .../lib/models/workspaces_page/workspace.dart | 57 ++ .../models/workspaces_page/workspaces.dart | 22 + .../workspaces_page/workspaces_object.dart | 17 + .../lib/providers/auth.dart | 120 +++ .../lib/providers/node_provider.dart | 165 ++++ .../lib/providers/profile_data_provider.dart | 33 + .../lib/providers/settings_provider.dart | 57 ++ .../lib/providers/user_provider.dart | 58 ++ .../lib/providers/workspace_provider.dart | 362 +++++++++ .../lib/screens/auth_screens/login_page.dart | 259 +++++++ .../auth_screens/please_login_page.dart | 48 ++ .../lib/screens/auth_screens/signup_page.dart | 350 +++++++++ .../widgets/login_page_appbar.dart | 19 + .../widgets/please_login_prompts.dart | 140 ++++ .../widgets/please_login_signup.dart | 75 ++ .../widgets/strong_password_checks.dart | 70 ++ .../lib/screens/builder_page.dart | 59 ++ .../lib/screens/graph_page/graph_page.dart | 112 +++ .../screens/graph_page/mobile_graph_page.dart | 202 +++++ .../screens/graph_page/web_graph_page.dart | 77 ++ .../graph_page/widgets/graph_node.dart | 83 ++ .../graph_page/widgets/graph_node_popup.dart | 171 +++++ .../widgets/graph_page_node_card.dart | 108 +++ .../screens/graph_page/widgets/node_list.dart | 77 ++ .../lib/screens/home_page/home_page.dart | 155 ++++ .../screens/home_page/mobile_home_page.dart | 83 ++ .../home_page/widgets/home_page_appbar.dart | 41 + .../widgets/home_page_node_card.dart | 81 ++ .../widgets/home_page_user_card.dart | 89 +++ .../screens/home_page/widgets/node_cards.dart | 40 + .../screens/home_page/widgets/user_cards.dart | 45 ++ .../node_details_page/node_details_page.dart | 195 +++++ .../widgets/contributors_list_view.dart | 68 ++ .../widgets/node_details.dart | 208 +++++ .../widgets/node_details_nav_bar_item.dart | 83 ++ .../widgets/node_details_tab_bar.dart | 79 ++ .../widgets/proof_list_view.dart | 78 ++ .../widgets/questions_list_view.dart | 61 ++ .../widgets/references_list_view.dart | 63 ++ .../widgets/suggestion_node_card.dart | 63 ++ .../widgets/you_may_like.dart | 68 ++ .../notifications_page.dart | 14 + .../page_with_appbar/page_with_appbar.dart | 62 ++ .../widgets/app_bar_button.dart | 39 + .../widgets/app_bar_logo.dart | 32 + .../widgets/profile_menu.dart | 104 +++ .../widgets/top_navigation_bar.dart | 162 ++++ .../profile_page/account_settings_page.dart | 18 + .../profile_page/change_password_page.dart | 20 + .../screens/profile_page/profile_page.dart | 338 ++++++++ .../profile_page/widgets/about_me.dart | 106 +++ .../profile_page/widgets/about_me_edit.dart | 37 + .../widgets/account_settings_form.dart | 184 +++++ .../widgets/change_password_form.dart | 128 ++++ .../widgets/desktop_edit_profile_button.dart | 55 ++ .../profile_page/widgets/logout_button.dart | 33 + .../widgets/mobile_edit_profile_button.dart | 37 + .../widgets/profile_activity_tabbar.dart | 61 ++ .../widgets/profile_nav_bar_item.dart | 83 ++ .../widgets/profile_node_card.dart | 71 ++ .../widgets/question_activity.dart | 60 ++ .../widgets/settings_input_widget.dart | 101 +++ .../mobile_create_workspace_page.dart | 25 + .../mobile_workspace_page.dart | 319 ++++++++ .../widget/app_alert_dialog.dart | 38 + .../widget/contributor_card.dart | 69 ++ .../widget/entry_card.dart | 285 +++++++ .../widget/mobile_workspace_content.dart | 339 ++++++++ .../widget/reference_card.dart | 96 +++ .../widget/subsection_title.dart | 29 + .../web_workspace_page.dart | 207 +++++ .../widgets/add_reference_form.dart | 146 ++++ .../widgets/contributors_list_view.dart | 135 ++++ .../widgets/create_workspace_form.dart | 42 + .../widgets/entries_list_view.dart | 165 ++++ .../widgets/entry_form.dart | 49 ++ .../widgets/references_list_view.dart | 110 +++ .../send_collaboration_request_form.dart | 135 ++++ .../widgets/workspaces_side_bar.dart | 171 +++++ .../workspace_page/workspaces_page.dart | 116 +++ .../lib/services/screen_navigation.dart | 70 ++ .../lib/services/share_page.dart | 11 + .../lib/utils/colors.dart | 18 + .../lib/utils/constants.dart | 5 + .../lib/utils/lorem_ipsum.dart | 30 + .../lib/utils/responsive/responsive.dart | 49 ++ .../lib/utils/router.dart | 185 +++++ .../lib/utils/text_styles.dart | 64 ++ .../lib/widgets/annotation_text.dart | 344 +++++++++ .../lib/widgets/app_button.dart | 66 ++ .../lib/widgets/app_search_bar.dart | 184 +++++ .../lib/widgets/app_text_field.dart | 66 ++ .../lib/widgets/card_container.dart | 56 ++ .../lib/widgets/search_bar_extended.dart | 246 ++++++ .../lib/widgets/simple_app_bar.dart | 32 + .../linux/.gitignore | 1 + .../linux/CMakeLists.txt | 139 ++++ .../linux/flutter/CMakeLists.txt | 88 +++ .../flutter/generated_plugin_registrant.cc | 15 + .../flutter/generated_plugin_registrant.h | 15 + .../linux/flutter/generated_plugins.cmake | 24 + .../linux/main.cc | 6 + .../linux/my_application.cc | 104 +++ .../linux/my_application.h | 18 + .../macos/.gitignore | 7 + .../macos/Flutter/Flutter-Debug.xcconfig | 2 + .../macos/Flutter/Flutter-Release.xcconfig | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 16 + .../macos/Podfile | 43 ++ .../macos/Runner.xcodeproj/project.pbxproj | 695 +++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 98 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../macos/Runner/AppDelegate.swift | 9 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 +++++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + .../macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 15 + .../macos/Runner/Release.entitlements | 8 + .../macos/RunnerTests/RunnerTests.swift | 12 + .../pubspec.lock | 674 ++++++++++++++++ .../pubspec.yaml | 103 +++ .../collaborative_science_platform/run.sh | 1 + .../test/widget_test.dart | 30 + .../web/favicon.png | Bin 0 -> 917 bytes .../web/icons/Icon-192.png | Bin 0 -> 5292 bytes .../web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes .../web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes .../web/index.html | 62 ++ .../web/manifest.json | 35 + .../windows/.gitignore | 17 + .../windows/CMakeLists.txt | 102 +++ .../windows/flutter/CMakeLists.txt | 104 +++ .../flutter/generated_plugin_registrant.cc | 17 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 25 + .../windows/runner/CMakeLists.txt | 40 + .../windows/runner/Runner.rc | 121 +++ .../windows/runner/flutter_window.cpp | 71 ++ .../windows/runner/flutter_window.h | 33 + .../windows/runner/main.cpp | 43 ++ .../windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 20 + .../windows/runner/utils.cpp | 65 ++ .../windows/runner/utils.h | 19 + .../windows/runner/win32_window.cpp | 288 +++++++ .../windows/runner/win32_window.h | 102 +++ 268 files changed, 16474 insertions(+) create mode 100644 project/FrontEnd/.idea/workspace.xml create mode 100644 project/FrontEnd/collaborative_science_platform/.gitignore create mode 100644 project/FrontEnd/collaborative_science_platform/.metadata create mode 100644 project/FrontEnd/collaborative_science_platform/Dockerfile create mode 100644 project/FrontEnd/collaborative_science_platform/README.md create mode 100644 project/FrontEnd/collaborative_science_platform/analysis_options.yaml create mode 100644 project/FrontEnd/collaborative_science_platform/android/.gitignore create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/build.gradle create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/debug/AndroidManifest.xml create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/AndroidManifest.xml create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/kotlin/com/example/collaborative_science_platform/MainActivity.kt create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/drawable/launch_background.xml create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/values-night/styles.xml create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/main/res/values/styles.xml create mode 100644 project/FrontEnd/collaborative_science_platform/android/app/src/profile/AndroidManifest.xml create mode 100644 project/FrontEnd/collaborative_science_platform/android/build.gradle create mode 100644 project/FrontEnd/collaborative_science_platform/android/gradle.properties create mode 100644 project/FrontEnd/collaborative_science_platform/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 project/FrontEnd/collaborative_science_platform/android/settings.gradle create mode 100644 project/FrontEnd/collaborative_science_platform/assets/images/.gitkeep create mode 100644 project/FrontEnd/collaborative_science_platform/assets/images/gumball.jpg create mode 100644 project/FrontEnd/collaborative_science_platform/assets/images/logo.svg create mode 100644 project/FrontEnd/collaborative_science_platform/assets/images/logo_small.svg create mode 100644 project/FrontEnd/collaborative_science_platform/ios/.gitignore create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Flutter/AppFrameworkInfo.plist create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Flutter/Debug.xcconfig create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Flutter/Release.xcconfig create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Podfile create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Podfile.lock create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.pbxproj create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/AppDelegate.swift create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Base.lproj/Main.storyboard create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Info.plist create mode 100644 project/FrontEnd/collaborative_science_platform/ios/Runner/Runner-Bridging-Header.h create mode 100644 project/FrontEnd/collaborative_science_platform/ios/RunnerTests/RunnerTests.swift create mode 100644 project/FrontEnd/collaborative_science_platform/lib/data/.gitkeep create mode 100644 project/FrontEnd/collaborative_science_platform/lib/exceptions/auth_exceptions.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/exceptions/node_details_exceptions.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/exceptions/profile_page_exceptions.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/exceptions/search_exceptions.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/exceptions/workspace_exceptions.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/extensions/.gitkeep create mode 100644 project/FrontEnd/collaborative_science_platform/lib/helpers/date_to_string.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/helpers/node_helper.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/helpers/search_helper.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/main.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/account.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/annotation.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/base_user.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/basic_user.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/collaboration_request.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/contributor_user.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/node.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/node_detailed.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/proof.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/node_details_page/question.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/notification.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/profile_data.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/proof.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/question.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/request.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/review.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/review_request.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/reviewer.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/semantic_tag.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/status.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/tag.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/theorem.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/user.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/wiki_tag.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/entry.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspace.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspaces.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/models/workspaces_page/workspaces_object.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/providers/auth.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/providers/node_provider.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/providers/profile_data_provider.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/providers/settings_provider.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/providers/user_provider.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/providers/workspace_provider.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/login_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/please_login_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/signup_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/login_page_appbar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/please_login_prompts.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/please_login_signup.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/auth_screens/widgets/strong_password_checks.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/builder_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/graph_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/mobile_graph_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/web_graph_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_node.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_node_popup.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/graph_page_node_card.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/graph_page/widgets/node_list.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/home_page/home_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/home_page/mobile_home_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_appbar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_node_card.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/home_page_user_card.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/node_cards.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/home_page/widgets/user_cards.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/node_details_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/contributors_list_view.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details_nav_bar_item.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/node_details_tab_bar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/proof_list_view.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/questions_list_view.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/references_list_view.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/suggestion_node_card.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/node_details_page/widgets/you_may_like.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/notifications_page/notifications_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/page_with_appbar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/app_bar_button.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/app_bar_logo.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/profile_menu.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/page_with_appbar/widgets/top_navigation_bar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/account_settings_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/change_password_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/profile_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/about_me.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/about_me_edit.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/account_settings_form.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/change_password_form.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/desktop_edit_profile_button.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/logout_button.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/mobile_edit_profile_button.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_activity_tabbar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_nav_bar_item.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/profile_node_card.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/question_activity.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/profile_page/widgets/settings_input_widget.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/create_workspace_page/mobile_create_workspace_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/mobile_workspace_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/app_alert_dialog.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/contributor_card.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/entry_card.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/mobile_workspace_content.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/reference_card.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/mobile_workspace_page/widget/subsection_title.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/web_workspace_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/add_reference_form.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/contributors_list_view.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/create_workspace_form.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/entries_list_view.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/entry_form.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/references_list_view.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/send_collaboration_request_form.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/web_workspace_page/widgets/workspaces_side_bar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/screens/workspace_page/workspaces_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/services/screen_navigation.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/services/share_page.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/utils/colors.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/utils/constants.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/utils/lorem_ipsum.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/utils/responsive/responsive.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/utils/router.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/utils/text_styles.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/widgets/annotation_text.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/widgets/app_button.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/widgets/app_search_bar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/widgets/app_text_field.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/widgets/card_container.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/widgets/search_bar_extended.dart create mode 100644 project/FrontEnd/collaborative_science_platform/lib/widgets/simple_app_bar.dart create mode 100644 project/FrontEnd/collaborative_science_platform/linux/.gitignore create mode 100644 project/FrontEnd/collaborative_science_platform/linux/CMakeLists.txt create mode 100644 project/FrontEnd/collaborative_science_platform/linux/flutter/CMakeLists.txt create mode 100644 project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugin_registrant.cc create mode 100644 project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugin_registrant.h create mode 100644 project/FrontEnd/collaborative_science_platform/linux/flutter/generated_plugins.cmake create mode 100644 project/FrontEnd/collaborative_science_platform/linux/main.cc create mode 100644 project/FrontEnd/collaborative_science_platform/linux/my_application.cc create mode 100644 project/FrontEnd/collaborative_science_platform/linux/my_application.h create mode 100644 project/FrontEnd/collaborative_science_platform/macos/.gitignore create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Flutter/Flutter-Release.xcconfig create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Podfile create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/project.pbxproj create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/AppDelegate.swift create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Debug.xcconfig create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Release.xcconfig create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Configs/Warnings.xcconfig create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/DebugProfile.entitlements create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Info.plist create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/MainFlutterWindow.swift create mode 100644 project/FrontEnd/collaborative_science_platform/macos/Runner/Release.entitlements create mode 100644 project/FrontEnd/collaborative_science_platform/macos/RunnerTests/RunnerTests.swift create mode 100644 project/FrontEnd/collaborative_science_platform/pubspec.lock create mode 100644 project/FrontEnd/collaborative_science_platform/pubspec.yaml create mode 100755 project/FrontEnd/collaborative_science_platform/run.sh create mode 100644 project/FrontEnd/collaborative_science_platform/test/widget_test.dart create mode 100644 project/FrontEnd/collaborative_science_platform/web/favicon.png create mode 100644 project/FrontEnd/collaborative_science_platform/web/icons/Icon-192.png create mode 100644 project/FrontEnd/collaborative_science_platform/web/icons/Icon-512.png create mode 100644 project/FrontEnd/collaborative_science_platform/web/icons/Icon-maskable-192.png create mode 100644 project/FrontEnd/collaborative_science_platform/web/icons/Icon-maskable-512.png create mode 100644 project/FrontEnd/collaborative_science_platform/web/index.html create mode 100644 project/FrontEnd/collaborative_science_platform/web/manifest.json create mode 100644 project/FrontEnd/collaborative_science_platform/windows/.gitignore create mode 100644 project/FrontEnd/collaborative_science_platform/windows/CMakeLists.txt create mode 100644 project/FrontEnd/collaborative_science_platform/windows/flutter/CMakeLists.txt create mode 100644 project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugin_registrant.cc create mode 100644 project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugin_registrant.h create mode 100644 project/FrontEnd/collaborative_science_platform/windows/flutter/generated_plugins.cmake create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/CMakeLists.txt create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/Runner.rc create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/flutter_window.cpp create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/flutter_window.h create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/main.cpp create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/resource.h create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/resources/app_icon.ico create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/runner.exe.manifest create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/utils.cpp create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/utils.h create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/win32_window.cpp create mode 100644 project/FrontEnd/collaborative_science_platform/windows/runner/win32_window.h 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 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1a103eb57d76d790fc4e65e9c69e4f9831299085 GIT binary patch literal 72114 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 J1Q`F{1OOQWC$<0p literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uuz(rC1}QWNE&K#jR^;j87-Auq zoUlN^K{r-Q+XN;zI ze|?*NFmgt#V#GwrSWaz^2G&@SBmck6ZcIFMww~vE<1E?M2#KUn1CzsB6D2+0SuRV@ zV2kK5HvIGB{HX-hQzs0*AB%5$9RJ@a;)Ahq#p$GSP91^&hi#6sg*;a~dt}4AclK>h z_3MoPRQ{i;==;*1S-mY<(JFzhAxMI&<61&m$J0NDHdJ3tYx~j0%M-uN6Zl8~_0DOkGXc0001@sz3l12C6Xg{AT~( zm6w64BA|AX`Ve)YY-glyudNN>MAfkXz-T7`_`fEolM;0T0BA)(02-OaW z0*cW7Z~ec94o8&g0D$N>b!COu{=m}^%oXZ4?T8ZyPZuGGBPBA7pbQMoV5HYhiT?%! zcae~`(QAN4&}-=#2f5fkn!SWGWmSeCISBcS=1-U|MEoKq=k?_x3apK>9((R zuu$9X?^8?@(a{qMS%J8SJPq))v}Q-ZyDm6Gbie0m92=`YlwnQPQP1kGSm(N2UJ3P6 z^{p-u)SSCTW~c1rw;cM)-uL2{->wCn2{#%;AtCQ!m%AakVs1K#v@(*-6QavyY&v&*wO_rCJXJuq$c$7ZjsW+pJo-$L^@!7X04CvaOpPyfw|FKvu;e(&Iw>Tbg zL}#8e^?X%TReXTt>gsBByt0kSU20oQx*~P=4`&tcZ7N6t-6LiK{LxX*p6}9c<0Pu^ zLx1w_P4P2V>bX=`F%v$#{sUDdF|;rbI{p#ZW`00Bgh(eB(nOIhy8W9T>3aQ=k8Z9% zB+TusFABF~J?N~fAd}1Rme=@4+1=M{^P`~se7}e3;mY0!%#MJf!XSrUC{0uZqMAd7%q zQY#$A>q}noIB4g54Ue)x>ofVm3DKBbUmS4Z-bm7KdKsUixva)1*&z5rgAG2gxG+_x zqT-KNY4g7eM!?>==;uD9Y4iI(Hu$pl8!LrK_Zb}5nv(XKW{9R144E!cFf36p{i|8pRL~p`_^iNo z{mf7y`#hejw#^#7oKPlN_Td{psNpNnM?{7{R-ICBtYxk>?3}OTH_8WkfaTLw)ZRTfxjW+0>gMe zpKg~`Bc$Y>^VX;ks^J0oKhB#6Ukt{oQhN+o2FKGZx}~j`cQB%vVsMFnm~R_1Y&Ml? zwFfb~d|dW~UktY@?zkau>Owe zRroi(<)c4Ux&wJfY=3I=vg)uh;sL(IYY9r$WK1$F;jYqq1>xT{LCkIMb3t2jN8d`9 z=4(v-z7vHucc_fjkpS}mGC{ND+J-hc_0Ix4kT^~{-2n|;Jmn|Xf9wGudDk7bi*?^+ z7fku8z*mbkGm&xf&lmu#=b5mp{X(AwtLTf!N`7FmOmX=4xwbD=fEo8CaB1d1=$|)+ z+Dlf^GzGOdlqTO8EwO?8;r+b;gkaF^$;+#~2_YYVH!hD6r;PaWdm#V=BJ1gH9ZK_9 zrAiIC-)z)hRq6i5+$JVmR!m4P>3yJ%lH)O&wtCyum3A*})*fHODD2nq!1@M>t@Za+ zH6{(Vf>_7!I-APmpsGLYpl7jww@s5hHOj5LCQXh)YAp+y{gG(0UMm(Ur z3o3n36oFwCkn+H*GZ-c6$Y!5r3z*@z0`NrB2C^q#LkOuooUM8Oek2KBk}o1PU8&2L z4iNkb5CqJWs58aR394iCU^ImDqV;q_Pp?pl=RB2372(Io^GA^+oKguO1(x$0<7w3z z)j{vnqEB679Rz4i4t;8|&Zg77UrklxY9@GDq(ZphH6=sW`;@uIt5B?7Oi?A0-BL}(#1&R;>2aFdq+E{jsvpNHjLx2t{@g1}c~DQcPNmVmy| zNMO@ewD^+T!|!DCOf}s9dLJU}(KZy@Jc&2Nq3^;vHTs}Hgcp`cw&gd7#N}nAFe3cM1TF%vKbKSffd&~FG9y$gLyr{#to)nxz5cCASEzQ}gz8O)phtHuKOW6p z@EQF(R>j%~P63Wfosrz8p(F=D|Mff~chUGn(<=CQbSiZ{t!e zeDU-pPsLgtc#d`3PYr$i*AaT!zF#23htIG&?QfcUk+@k$LZI}v+js|yuGmE!PvAV3 ztzh90rK-0L6P}s?1QH`Ot@ilbgMBzWIs zIs6K<_NL$O4lwR%zH4oJ+}JJp-bL6~%k&p)NGDMNZX7)0kni&%^sH|T?A)`z z=adV?!qnWx^B$|LD3BaA(G=ePL1+}8iu^SnnD;VE1@VLHMVdSN9$d)R(Wk{JEOp(P zm3LtAL$b^*JsQ0W&eLaoYag~=fRRdI>#FaELCO7L>zXe6w*nxN$Iy*Q*ftHUX0+N- zU>{D_;RRVPbQ?U+$^%{lhOMKyE5>$?U1aEPist+r)b47_LehJGTu>TcgZe&J{ z{q&D{^Ps~z7|zj~rpoh2I_{gAYNoCIJmio3B}$!5vTF*h$Q*vFj~qbo%bJCCRy509 zHTdDh_HYH8Zb9`}D5;;J9fkWOQi%Y$B1!b9+ESj+B@dtAztlY2O3NE<6HFiqOF&p_ zW-K`KiY@RPSY-p9Q99}Hcd05DT79_pfb{BV7r~?9pWh=;mcKBLTen%THFPo2NN~Nf zriOtFnqx}rtO|A6k!r6 zf-z?y-UD{dT0kT9FJ`-oWuPHbo+3wBS(}?2ql(+e@VTExmfnB*liCb zmeI+v5*+W_L;&kQN^ChW{jE0Mw#0Tfs}`9bk3&7UjxP^Ke(%eJu2{VnW?tu7Iqecm zB5|=-QdzK$=h50~{X3*w4%o1FS_u(dG2s&427$lJ?6bkLet}yYXCy)u_Io1&g^c#( z-$yYmSpxz{>BL;~c+~sxJIe1$7eZI_9t`eB^Pr0)5CuA}w;;7#RvPq|H6!byRzIJG ziQ7a4y_vhj(AL`8PhIm9edCv|%TX#f50lt8+&V+D4<}IA@S@#f4xId80oH$!_!q?@ zFRGGg2mTv&@76P7aTI{)Hu%>3QS_d)pQ%g8BYi58K~m-Ov^7r8BhX7YC1D3vwz&N8{?H*_U7DI?CI)+et?q|eGu>42NJ?K4SY zD?kc>h@%4IqNYuQ8m10+8xr2HYg2qFNdJl=Tmp&ybF>1>pqVfa%SsV*BY$d6<@iJA ziyvKnZ(~F9xQNokBgMci#pnZ}Igh0@S~cYcU_2Jfuf|d3tuH?ZSSYBfM(Y3-JBsC|S9c;# zyIMkPxgrq};0T09pjj#X?W^TFCMf1-9P{)g88;NDI+S4DXe>7d3Mb~i-h&S|Jy{J< zq3736$bH?@{!amD!1Ys-X)9V=#Z={fzsjVYMX5BG6%}tkzwC#1nQLj1y1f#}8**4Y zAvDZHw8)N)8~oWC88CgzbwOrL9HFbk4}h85^ptuu7A+uc#$f^9`EWv1Vr{5+@~@Uv z#B<;-nt;)!k|fRIg;2DZ(A2M2aC65kOIov|?Mhi1Sl7YOU4c$T(DoRQIGY`ycfkn% zViHzL;E*A{`&L?GP06Foa38+QNGA zw3+Wqs(@q+H{XLJbwZzE(omw%9~LPZfYB|NF5%j%E5kr_xE0u;i?IOIchn~VjeDZ) zAqsqhP0vu2&Tbz3IgJvMpKbThC-@=nk)!|?MIPP>MggZg{cUcKsP8|N#cG5 zUXMXxcXBF9`p>09IR?x$Ry3;q@x*%}G#lnB1}r#!WL88I@uvm}X98cZ8KO&cqT1p> z+gT=IxPsq%n4GWgh-Bk8E4!~`r@t>DaQKsjDqYc&h$p~TCh8_Mck5UB84u6Jl@kUZCU9BA-S!*bf>ZotFX9?a_^y%)yH~rsAz0M5#^Di80_tgoKw(egN z`)#(MqAI&A84J#Z<|4`Co8`iY+Cv&iboMJ^f9ROUK0Lm$;-T*c;TCTED_0|qfhlcS zv;BD*$Zko#nWPL}2K8T-?4}p{u)4xon!v_(yVW8VMpxg4Kh^J6WM{IlD{s?%XRT8P|yCU`R&6gwB~ zg}{At!iWCzOH37!ytcPeC`(({ovP7M5Y@bYYMZ}P2Z3=Y_hT)4DRk}wfeIo%q*M9UvXYJq!-@Ly79m5aLD{hf@BzQB>FdQ4mw z6$@vzSKF^Gnzc9vbccii)==~9H#KW<6)Uy1wb~auBn6s`ct!ZEos`WK8e2%<00b%# zY9Nvnmj@V^K(a_38dw-S*;G-(i(ETuIwyirs?$FFW@|66a38k+a%GLmucL%Wc8qk3 z?h_4!?4Y-xt)ry)>J`SuY**fuq2>u+)VZ+_1Egzctb*xJ6+7q`K$^f~r|!i?(07CD zH!)C_uerf-AHNa?6Y61D_MjGu*|wcO+ZMOo4q2bWpvjEWK9yASk%)QhwZS%N2_F4& z16D18>e%Q1mZb`R;vW{+IUoKE`y3(7p zplg5cBB)dtf^SdLd4n60oWie|(ZjgZa6L*VKq02Aij+?Qfr#1z#fwh92aV-HGd^_w zsucG24j8b|pk>BO7k8dS86>f-jBP^Sa}SF{YNn=^NU9mLOdKcAstv&GV>r zLxKHPkFxpvE8^r@MSF6UA}cG`#yFL8;kA7ccH9D=BGBtW2;H>C`FjnF^P}(G{wU;G z!LXLCbPfsGeLCQ{Ep$^~)@?v`q(uI`CxBY44osPcq@(rR-633!qa zsyb>?v%@X+e|Mg`+kRL*(;X>^BNZz{_kw5+K;w?#pReiw7eU8_Z^hhJ&fj80XQkuU z39?-z)6Fy$I`bEiMheS(iB6uLmiMd1i)cbK*9iPpl+h4x9ch7x- z1h4H;W_G?|)i`z??KNJVwgfuAM=7&Apd3vm#AT8uzQZ!NII}}@!j)eIfn53h{NmN7 zAKG6SnKP%^k&R~m5#@_4B@V?hYyHkm>0SQ@PPiw*@Tp@UhP-?w@jW?nxXuCipMW=L zH*5l*d@+jXm0tIMP_ec6Jcy6$w(gKK@xBX8@%oPaSyG;13qkFb*LuVx3{AgIyy&n3 z@R2_DcEn|75_?-v5_o~%xEt~ONB>M~tpL!nOVBLPN&e5bn5>+7o0?Nm|EGJ5 zmUbF{u|Qn?cu5}n4@9}g(G1JxtzkKv(tqwm_?1`?YSVA2IS4WI+*(2D*wh&6MIEhw z+B+2U<&E&|YA=3>?^i6)@n1&&;WGHF-pqi_sN&^C9xoxME5UgorQ_hh1__zzR#zVC zOQt4q6>ME^iPJ37*(kg4^=EFqyKH@6HEHXy79oLj{vFqZGY?sVjk!BX^h$SFJlJnv z5uw~2jLpA)|0=tp>qG*tuLru?-u`khGG2)o{+iDx&nC}eWj3^zx|T`xn5SuR;Aw8U z`p&>dJw`F17@J8YAuW4=;leBE%qagVTG5SZdh&d)(#ZhowZ|cvWvGMMrfVsbg>_~! z19fRz8CSJdrD|Rl)w!uznBF&2-dg{>y4l+6(L(vzbLA0Bk&`=;oQQ>(M8G=3kto_) zP8HD*n4?MySO2YrG6fwSrVmnesW+D&fxjfEmp=tPd?RKLZJcH&K(-S+x)2~QZ$c(> zru?MND7_HPZJVF%wX(49H)+~!7*!I8w72v&{b={#l9yz+S_aVPc_So%iF8>$XD1q1 zFtucO=rBj0Ctmi0{njN8l@}!LX}@dwl>3yMxZ;7 z0Ff2oh8L)YuaAGOuZ5`-p%Z4H@H$;_XRJQ|&(MhO78E|nyFa158gAxG^SP(vGi^+< zChY}o(_=ci3Wta#|K6MVljNe0T$%Q5ylx-v`R)r8;3+VUpp-)7T`-Y&{Zk z*)1*2MW+_eOJtF5tCMDV`}jg-R(_IzeE9|MBKl;a7&(pCLz}5<Zf+)T7bgNUQ_!gZtMlw=8doE}#W+`Xp~1DlE=d5SPT?ymu!r4z%&#A-@x^=QfvDkfx5-jz+h zoZ1OK)2|}_+UI)i9%8sJ9X<7AA?g&_Wd7g#rttHZE;J*7!e5B^zdb%jBj&dUDg4&B zMMYrJ$Z%t!5z6=pMGuO-VF~2dwjoXY+kvR>`N7UYfIBMZGP|C7*O=tU z2Tg_xi#Q3S=1|=WRfZD;HT<1D?GMR%5kI^KWwGrC@P2@R>mDT^3qsmbBiJc21kip~ zZp<7;^w{R;JqZ)C4z-^wL=&dBYj9WJBh&rd^A^n@07qM$c+kGv^f+~mU5_*|eePF| z3wDo-qaoRjmIw<2DjMTG4$HP{z54_te_{W^gu8$r=q0JgowzgQPct2JNtWPUsjF8R zvit&V8$(;7a_m%%9TqPkCXYUp&k*MRcwr*24>hR! z$4c#E=PVE=P4MLTUBM z7#*RDe0}=B)(3cvNpOmWa*eH#2HR?NVqXdJ=hq);MGD07JIQQ7Y0#iD!$C+mk7x&B zMwkS@H%>|fmSu#+ zI!}Sb(%o29Vkp_Th>&&!k7O>Ba#Om~B_J{pT7BHHd8(Ede(l`7O#`_}19hr_?~JP9 z`q(`<)y>%)x;O7)#-wfCP{?llFMoH!)ZomgsOYFvZ1DxrlYhkWRw#E-#Qf*z@Y-EQ z1~?_=c@M4DO@8AzZ2hKvw8CgitzI9yFd&N1-{|vP#4IqYb*#S0e3hrjsEGlnc4xwk z4o!0rxpUt8j&`mJ8?+P8G{m^jbk)bo_UPM+ifW*y-A*et`#_Ja_3nYyRa9fAG1Xr5 z>#AM_@PY|*u)DGRWJihZvgEh#{*joJN28uN7;i5{kJ*Gb-TERfN{ERe_~$Es~NJCpdKLRvdj4658uYYx{ng7I<6j~w@p%F<7a(Ssib|j z51;=Py(Nu*#hnLx@w&8X%=jrADn3TW>kplnb zYbFIWWVQXN7%Cwn6KnR)kYePEBmvM45I)UJb$)ninpdYg3a5N6pm_7Q+9>!_^xy?k za8@tJ@OOs-pRAAfT>Nc2x=>sZUs2!9Dwa%TTmDggH4fq(x^MW>mcRyJINlAqK$YQCMgR8`>6=Sg$ zFnJZsA8xUBXIN3i70Q%8px@yQPMgVP=>xcPI38jNJK<=6hC={a07+n@R|$bnhB)X$ z(Zc%tadp70vBTnW{OUIjTMe38F}JIH$#A}PB&RosPyFZMD}q}5W%$rh>5#U;m`z2K zc(&WRxx7DQLM-+--^w*EWAIS%bi>h587qkwu|H=hma3T^bGD&Z!`u(RKLeNZ&pI=q$|HOcji(0P1QC!YkAp*u z3%S$kumxR}jU<@6`;*-9=5-&LYRA<~uFrwO3U0k*4|xUTp4ZY7;Zbjx|uw&BWU$zK(w55pWa~#=f$c zNDW0O68N!xCy>G}(CX=;8hJLxAKn@Aj(dbZxO8a$+L$jK8$N-h@4$i8)WqD_%Snh4 zR?{O%k}>lr>w$b$g=VP8mckcCrjnp>uQl5F_6dPM8FWRqs}h`DpfCv20uZhyY~tr8 zkAYW4#yM;*je)n=EAb(q@5BWD8b1_--m$Q-3wbh1hM{8ihq7UUQfg@)l06}y+#=$( z$x>oVYJ47zAC^>HLRE-!HitjUixP6!R98WU+h>zct7g4eD;Mj#FL*a!VW!v-@b(Jv zj@@xM5noCp5%Vk3vY{tyI#oyDV7<$`KG`tktVyC&0DqxA#>V;-3oH%NW|Q&=UQ&zU zXNIT67J4D%5R1k#bW0F}TD`hlW7b)-=-%X4;UxQ*u4bK$mTAp%y&-(?{sXF%e_VH6 zTkt(X)SSN|;8q@8XX6qfR;*$r#HbIrvOj*-5ND8RCrcw4u8D$LXm5zlj@E5<3S0R# z??=E$p{tOk96$SloZ~ARe5`J=dB|Nj?u|zy2r(-*(q^@YwZiTF@QzQyPx_l=IDKa) zqD@0?IHJqSqZ_5`)81?4^~`yiGh6>7?|dKa8!e|}5@&qV!Iu9<@G?E}Vx9EzomB3t zEbMEm$TKGwkHDpirp;FZD#6P5qIlQJ8}rf;lHoz#h4TFFPYmS3+8(13_Mx2`?^=8S z|0)0&dQLJTU6{b%*yrpQe#OKKCrL8}YKw+<#|m`SkgeoN69TzIBQOl_Yg)W*w?NW) z*WxhEp$zQBBazJSE6ygu@O^!@Fr46j=|K`Mmb~xbggw7<)BuC@cT@Bwb^k?o-A zKX^9AyqR?zBtW5UA#siILztgOp?r4qgC`9jYJG_fxlsVSugGprremg-W(K0{O!Nw-DN%=FYCyfYA3&p*K>+|Q}s4rx#CQK zNj^U;sLM#q8}#|PeC$p&jAjqMu(lkp-_50Y&n=qF9`a3`Pr9f;b`-~YZ+Bb0r~c+V z*JJ&|^T{}IHkwjNAaM^V*IQ;rk^hnnA@~?YL}7~^St}XfHf6OMMCd9!vhk#gRA*{L zp?&63axj|Si%^NW05#87zpU_>QpFNb+I00v@cHwvdBn+Un)n2Egdt~LcWOeBW4Okm zD$-e~RD+W|UB;KQ;a7GOU&%p*efGu2$@wR74+&iP8|6#_fmnh^WcJLs)rtz{46);F z4v0OL{ZP9550>2%FE(;SbM*#sqMl*UXOb>ch`fJ|(*bOZ9=EB1+V4fkQ)hjsm3-u^Pk-4ji_uDDHdD>84tER!MvbH`*tG zzvbhBR@}Yd`azQGavooV=<WbvWLlO#x`hyO34mKcxrGv=`{ssnP=0Be5#1B;Co9 zh{TR>tjW2Ny$ZxJpYeg57#0`GP#jxDCU0!H15nL@@G*HLQcRdcsUO3sO9xvtmUcc{F*>FQZcZ5bgwaS^k-j5mmt zI7Z{Xnoml|A(&_{imAjK!kf5>g(oDqDI4C{;Bv162k8sFNr;!qPa2LPh>=1n z=^_9)TsLDvTqK7&*Vfm5k;VXjBW^qN3Tl&}K=X5)oXJs$z3gk0_+7`mJvz{pK|FVs zHw!k&7xVjvY;|(Py<;J{)b#Yjj*LZO7x|~pO4^MJ2LqK3X;Irb%nf}L|gck zE#55_BNsy6m+W{e zo!P59DDo*s@VIi+S|v93PwY6d?CE=S&!JLXwE9{i)DMO*_X90;n2*mPDrL%{iqN!?%-_95J^L z=l<*{em(6|h7DR4+4G3Wr;4*}yrBkbe3}=p7sOW1xj!EZVKSMSd;QPw>uhKK z#>MlS@RB@-`ULv|#zI5GytO{=zp*R__uK~R6&p$q{Y{iNkg61yAgB8C^oy&``{~FK z8hE}H&nIihSozKrOONe5Hu?0Zy04U#0$fB7C6y~?8{or}KNvP)an=QP&W80mj&8WL zEZQF&*FhoMMG6tOjeiCIV;T{I>jhi9hiUwz?bkX3NS-k5eWKy)Mo_orMEg4sV6R6X&i-Q%JG;Esl+kLpn@Bsls9O|i9z`tKB^~1D5)RIBB&J<6T@a4$pUvh$IR$%ubH)joi z!7>ON0DPwx=>0DA>Bb^c?L8N0BBrMl#oDB+GOXJh;Y&6I)#GRy$W5xK%a;KS8BrER zX)M>Rdoc*bqP*L9DDA3lF%U8Yzb6RyIsW@}IKq^i7v&{LeIc=*ZHIbO68x=d=+0T( zev=DT9f|x!IWZNTB#N7}V4;9#V$%Wo0%g>*!MdLOEU>My0^gni9ocID{$g9ytD!gy zKRWT`DVN(lcYjR|(}f0?zgBa3SwunLfAhx><%u0uFkrdyqlh8_g zDKt#R6rA2(Vm2LW_>3lBNYKG_F{TEnnKWGGC15y&OebIRhFL4TeMR*v9i0wPoK#H< zu4){s4K&K)K(9~jgGm;H7lS7y_RYfS;&!Oj5*eqbvEcW^a*i67nevzOZxN6F+K~A%TYEtsAVsR z@J=1hc#Dgs7J2^FL|qV&#WBFQyDtEQ2kPO7m2`)WFhqAob)Y>@{crkil6w9VoA?M6 zADGq*#-hyEVhDG5MQj677XmcWY1_-UO40QEP&+D)rZoYv^1B_^w7zAvWGw&pQyCyx zD|ga$w!ODOxxGf_Qq%V9Z7Q2pFiUOIK818AGeZ-~*R zI1O|SSc=3Z?#61Rd|AXx2)K|F@Z1@x!hBBMhAqiU)J=U|Y)T$h3D?ZPPQgkSosnN! zIqw-t$0fqsOlgw3TlHJF*t$Q@bg$9}A3X=cS@-yU3_vNG_!#9}7=q7!LZ?-%U26W4 z$d>_}*s1>Ac%3uFR;tnl*fNlylJ)}r2^Q3&@+is3BIv<}x>-^_ng;jhdaM}6Sg3?p z0jS|b%QyScy3OQ(V*~l~bK>VC{9@FMuW_JUZO?y(V?LKWD6(MXzh}M3r3{7b4eB(#`(q1m{>Be%_<9jw8HO!x#yF6vez$c#kR+}s zZO-_;25Sxngd(}){zv?ccbLqRAlo;yog>4LH&uZUK1n>x?u49C)Y&2evH5Zgt~666 z_2_z|H5AO5Iqxv_Bn~*y1qzRPcob<+Otod5Xd2&z=C;u+F}zBB@b^UdGdUz|s!H}M zXG%KiLzn3G?FZgdY&3pV$nSeY?ZbU^jhLz9!t0K?ep}EFNqR1@E!f*n>x*!uO*~JF zW9UXWrVgbX1n#76_;&0S7z}(5n-bqnII}_iDsNqfmye@)kRk`w~1 z6j4h4BxcPe6}v)xGm%=z2#tB#^KwbgMTl2I*$9eY|EWAHFc3tO48Xo5rW z5oHD!G4kb?MdrOHV=A+8ThlIqL8Uu+7{G@ zb)cGBm|S^Eh5= z^E^SZ=yeC;6nNCdztw&TdnIz}^Of@Ke*@vjt)0g>Y!4AJvWiL~e7+9#Ibhe)> ziNwh>gWZL@FlWc)wzihocz+%+@*euwXhW%Hb>l7tf8aJe5_ZSH1w-uG|B;9qpcBP0 zM`r1Hu#htOl)4Cl1c7oY^t0e4Jh$-I(}M5kzWqh{F=g&IM#JiC`NDSd@BCKX#y<P@Gwl$3a3w z6<(b|K(X5FIR22M)sy$4jY*F4tT{?wZRI+KkZFb<@j@_C316lu1hq2hA|1wCmR+S@ zRN)YNNE{}i_H`_h&VUT5=Y(lN%m?%QX;6$*1P}K-PcPx>*S55v)qZ@r&Vcic-sjkm z! z=nfW&X`}iAqa_H$H%z3Tyz5&P3%+;93_0b;zxLs)t#B|up}JyV$W4~`8E@+BHQ+!y zuIo-jW!~)MN$2eHwyx-{fyGjAWJ(l8TZtUp?wZWBZ%}krT{f*^fqUh+ywHifw)_F> zp76_kj_B&zFmv$FsPm|L7%x-j!WP>_P6dHnUTv!9ZWrrmAUteBa`rT7$2ixO;ga8U z3!91micm}{!Btk+I%pMgcKs?H4`i+=w0@Ws-CS&n^=2hFTQ#QeOmSz6ttIkzmh^`A zYPq)G1l3h(E$mkyr{mvz*MP`x+PULBn%CDhltKkNo6Uqg!vJ#DA@BIYr9TQ`18Un2 zv$}BYzOQuay9}w(?JV63F$H6WmlYPPpH=R|CPb%C@BCv|&Q|&IcW7*LX?Q%epS z`=CPx{1HnJ9_46^=0VmNb>8JvMw-@&+V8SDLRYsa>hZXEeRbtf5eJ>0@Ds47zIY{N z42EOP9J8G@MXXdeiPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$^8f$?lu1NER9Fe^SItioK@|V(ZWmgL zZT;XwPgVuWM>O%^|Dc$VK;n&?9!&g5)aVsG8cjs5UbtxVVnQNOV~7Mrg3+jnU;rhE z6fhW6P)R>_eXrXo-RW*y6RQ_qcb^s1wTu$TwriZ`=JUws>vRi}5x}MW1MR#7p|gIWJlaLK;~xaN}b< z<-@=RX-%1mt`^O0o^~2=CD7pJ<<$Rp-oUL-7PuG>do^5W_Mk#unlP}6I@6NPxY`Q} zuXJF}!0l)vwPNAW;@5DjPRj?*rZxl zwn;A(cFV!xe^CUu+6SrN?xe#mz?&%N9QHf~=KyK%DoB8HKC)=w=3E?1Bqj9RMJs3U z5am3Uv`@+{jgqO^f}Lx_Jp~CoP3N4AMZr~4&d)T`R?`(M{W5WWJV^z~2B|-oih@h^ zD#DuzGbl(P5>()u*YGo*Och=oRr~3P1wOlKqI)udc$|)(bacG5>~p(y>?{JD7nQf_ z*`T^YL06-O>T(s$bi5v~_fWMfnE7Vn%2*tqV|?~m;wSJEVGkNMD>+xCu#um(7}0so zSEu7?_=Q64Q5D+fz~T=Rr=G_!L*P|(-iOK*@X8r{-?oBlnxMNNgCVCN9Y~ocu+?XA zjjovJ9F1W$Nf!{AEv%W~8oahwM}4Ruc+SLs>_I_*uBxdcn1gQ^2F8a*vGjgAXYyh? zWCE@c5R=tbD(F4nL9NS?$PN1V_2*WR?gjv3)4MQeizuH`;sqrhgykEzj z593&TGlm3h`sIXy_U<7(dpRXGgp0TB{>s?}D{fwLe>IV~exweOfH!qM@CV5kib!YA z6O0gvJi_0J8IdEvyP#;PtqP*=;$iI2t(xG2YI-e!)~kaUn~b{6(&n zp)?iJ`z2)Xh%sCV@BkU`XL%_|FnCA?cVv@h*-FOZhY5erbGh)%Q!Av#fJM3Csc_g zC2I6x%$)80`Tkz#KRA!h1FzY`?0es3t!rKDT5EjPe6B=BLPr7s0GW!if;Ip^!AmGW zL;$`Vdre+|FA!I4r6)keFvAx3M#1`}ijBHDzy)3t0gwjl|qC2YB`SSxFKHr(oY#H$)x{L$LL zBdLKTlsOrmb>T0wd=&6l3+_Te>1!j0OU8%b%N342^opKmT)gni(wV($s(>V-fUv@0p8!f`=>PxC|9=nu ze{ToBBj8b<{PLfXV$h8YPgA~E!_sF9bl;QOF{o6t&JdsX?}rW!_&d`#wlB6T_h;Xf zl{4Tz5>qjF4kZgjO7ZiLPRz_~U@k5%?=30+nxEh9?s78gZ07YHB`FV`4%hlQlMJe@J`+e(qzy+h(9yY^ckv_* zb_E6o4p)ZaWfraIoB2)U7_@l(J0O%jm+Or>8}zSSTkM$ASG^w3F|I? z$+eHt7T~04(_WfKh27zqS$6* zzyy-ZyqvSIZ0!kkSvHknm_P*{5TKLQs8S6M=ONuKAUJWtpxbL#2(_huvY(v~Y%%#~ zYgsq$JbLLprKkV)32`liIT$KKEqs$iYxjFlHiRNvBhxbDg*3@Qefw4UM$>i${R5uB zhvTgmqQsKA{vrKN;TSJU2$f9q=y{$oH{<)woSeV>fkIz6D8@KB zf4M%v%f5U2?<8B(xn}xV+gWP?t&oiapJhJbfa;agtz-YM7=hrSuxl8lAc3GgFna#7 zNjX7;`d?oD`#AK+fQ=ZXqfIZFEk{ApzjJF0=yO~Yj{7oQfXl+6v!wNnoqwEvrs81a zGC?yXeSD2NV!ejp{LdZGEtd1TJ)3g{P6j#2jLR`cpo;YX}~_gU&Gd<+~SUJVh+$7S%`zLy^QqndN<_9 zrLwnXrLvW+ew9zX2)5qw7)zIYawgMrh`{_|(nx%u-ur1B7YcLp&WFa24gAuw~& zKJD3~^`Vp_SR$WGGBaMnttT)#fCc^+P$@UHIyBu+TRJWbcw4`CYL@SVGh!X&y%!x~ zaO*m-bTadEcEL6V6*{>irB8qT5Tqd54TC4`h`PVcd^AM6^Qf=GS->x%N70SY-u?qr>o2*OV7LQ=j)pQGv%4~z zz?X;qv*l$QSNjOuQZ>&WZs2^@G^Qas`T8iM{b19dS>DaXX~=jd4B2u`P;B}JjRBi# z_a@&Z5ev1-VphmKlZEZZd2-Lsw!+1S60YwW6@>+NQ=E5PZ+OUEXjgUaXL-E0fo(E* zsjQ{s>n33o#VZm0e%H{`KJi@2ghl8g>a~`?mFjw+$zlt|VJhSU@Y%0TWs>cnD&61fW4e0vFSaXZa4-c}U{4QR8U z;GV3^@(?Dk5uc@RT|+5C8-24->1snH6-?(nwXSnPcLn#X_}y3XS)MI_?zQ$ZAuyg+ z-pjqsw}|hg{$~f0FzmmbZzFC0He_*Vx|_uLc!Ffeb8#+@m#Z^AYcWcZF(^Os8&Z4g zG)y{$_pgrv#=_rV^D|Y<_b@ICleUv>c<0HzJDOsgJb#Rd-Vt@+EBDPyq7dUM9O{Yp zuGUrO?ma2wpuJuwl1M=*+tb|qx7Doj?!F-3Z>Dq_ihFP=d@_JO;vF{iu-6MWYn#=2 zRX6W=`Q`q-+q@Db|6_a1#8B|#%hskH82lS|9`im0UOJn?N#S;Y0$%xZw3*jR(1h5s z?-7D1tnIafviko>q6$UyqVDq1o@cwyCb*})l~x<@s$5D6N=-Uo1yc49p)xMzxwnuZ zHt!(hu-Ek;Fv4MyNTgbW%rPF*dB=;@r3YnrlFV{#-*gKS_qA(G-~TAlZ@Ti~Yxw;k za1EYyX_Up|`rpbZ0&Iv#$;eC|c0r4XGaQ-1mw@M_4p3vKIIpKs49a8Ns#ni)G314Z z8$Ei?AhiT5dQGWUYdCS|IC7r z=-8ol>V?u!n%F*J^^PZ(ONT&$Ph;r6X;pj|03HlDY6r~0g~X#zuzVU%a&!fs_f|m?qYvg^Z{y?9Qh7Rn?T*F%7lUtA6U&={HzhYEzA`knx1VH> z{tqv?p@I(&ObD5L4|YJV$QM>Nh-X3cx{I&!$FoPC_2iIEJfPk-$;4wz>adRu@n`_y z_R6aN|MDHdK;+IJmyw(hMoDCFCQ(6?hCAG5&7p{y->0Uckv# zvooVuu04$+pqof777ftk<#42@KQ((5DPcSMQyzGOJ{e9H$a9<2Qi_oHjl{#=FUL9d z+~0^2`tcvmp0hENwfHR`Ce|<1S@p;MNGInXCtHnrDPXCKmMTZQ{HVm_cZ>@?Wa6}O zHsJc7wE)mc@1OR2DWY%ZIPK1J2p6XDO$ar`$RXkbW}=@rFZ(t85AS>>U0!yt9f49^ zA9@pc0P#k;>+o5bJfx0t)Lq#v4`OcQn~av__dZ-RYOYu}F#pdsl31C^+Qgro}$q~5A<*c|kypzd} ziYGZ~?}5o`S5lw^B{O@laad9M_DuJle- z*9C7o=CJh#QL=V^sFlJ0c?BaB#4bV^T(DS6&Ne&DBM_3E$S^S13qC$7_Z?GYXTpR@wqr70wu$7+qvf-SEUa5mdHvFbu^7ew!Z1a^ zo}xKOuT*gtGws-a{Tx}{#(>G~Y_h&5P@Q8&p!{*s37^QX_Ibx<6XU*AtDOIvk|^{~ zPlS}&DM5$Ffyu-T&0|KS;Wnaqw{9DB&B3}vcO14wn;)O_e@2*9B&0I_ zZz{}CMxx`hv-XouY>^$Y@J(_INeM>lIQI@I>dBAqq1)}?Xmx(qRuX^i4IV%=MF306 z9g)i*79pP%_7Ex?m6ag-4Tlm=Z;?DQDyC-NpUIb#_^~V_tsL<~5<&;Gf2N+p?(msn zzUD~g>OoW@O}y0@Z;RN)wjam`CipmT&O7a|YljZqU=U86 zedayEdY)2F#BJ6xvmW8K&ffdS*0!%N<%RB!2~PAT4AD*$W7yzHbX#Eja9%3aD+Ah2 zf#T;XJW-GMxpE=d4Y>}jE=#U`IqgSoWcuvgaWQ9j1CKzG zDkoMDDT)B;Byl3R2PtC`ip=yGybfzmVNEx{xi_1|Cbqj>=FxQc{g`xj6fIfy`D8fA z##!-H_e6o0>6Su&$H2kQTujtbtyNFeKc}2=|4IfLTnye#@$Au7Kv4)dnA;-fz@D_8 z)>irG$)dkBY~zX zC!ZXLy*L3xr6cb70QqfN#Q>lFIc<>}>la4@3%7#>a1$PU&O^&VszpxLC%*!m-cO{B z-Y}rQr4$84(hvy#R69H{H zJ*O#uJh)TF6fbXy;fZkk%X=CjsTK}o5N1a`d7kgYYZLPxsHx%9*_XN8VWXEkVJZ%A z1A+5(B;0^{T4aPYr8%i@i32h)_)|q?9vws)r+=5u)1YNftF5mknwfd*%jXA2TeP}Z zQ!m?xJ3?9LpPM?_A3$hQ1QxNbR&}^m z!F999s?p^ak#C4NM_x2p9FoXWJ$>r?lJ)2bG)sX{gExgLA2s5RwHV!h6!C~d_H||J z>9{E{mEv{Z1z~65Vix@dqM4ZqiU|!)eWX$mwS5mLSufxbpBqqS!jShq1bmwCR6 z4uBri7ezMeS6ycaXPVu(i2up$L; zjpMtB`k~WaNrdgM_R=e#SN?Oa*u%nQy01?()h4A(jyfeNfx;5o+kX?maO4#1A^L}0 zYNyIh@QVXIFiS0*tE}2SWTrWNP3pH}1Vz1;E{@JbbgDFM-_Mky^7gH}LEhl~Ve5PexgbIyZ(IN%PqcaV@*_`ZFb=`EjspSz%5m2E34BVT)d=LGyHVz@-e%9Ova*{5@RD;7=Ebkc2GP%pIP^P7KzKapnh`UpH?@h z$RBpD*{b?vhohOKf-JG3?A|AX|2pQ?(>dwIbWhZ38GbTm4AImRNdv_&<99ySX;kJ| zo|5YgbHZC#HYgjBZrvGAT4NZYbp}qkVSa;C-LGsR26Co+i_HM&{awuO9l)Ml{G8zD zs$M8R`r+>PT#Rg!J(K6T4xHq7+tscU(}N$HY;Yz*cUObX7J7h0#u)S7b~t^Oj}TBF zuzsugnst;F#^1jm>22*AC$heublWtaQyM6RuaquFd8V#hJ60Z3j7@bAs&?dD#*>H0SJaDwp%U~27>zdtn+ z|8sZzklZy$%S|+^ie&P6++>zbrq&?+{Yy11Y>@_ce@vU4ZulS@6yziG6;iu3Iu`M= zf3rcWG<+3F`K|*(`0mE<$89F@jSq;j=W#E>(R}2drCB7D*0-|D;S;(;TwzIJkGs|q z2qH{m_zZ+el`b;Bv-#bQ>}*VPYC|7`rgBFf2oivXS^>v<&HHTypvd4|-zn|=h=TG{ z05TH2+{T%EnADO>3i|CB zCu60#qk`}GW{n4l-E$VrqgZGbI zbQW690KgZt4U3F^5@bdO1!xu~p@7Y~*_FfWg2CdvED5P5#w#V46LH`<&V0{t&Ml~4 zHNi7lIa+#i+^Z6EnxO7KJQw)wD)4~&S-Ki8)3=jpqxmx6c&zU&<&h%*c$I(5{1HZT zc9WE}ijcWJiVa^Q^xC|WX0habl89qycOyeViIbi(LFsEY_8a|+X^+%Qv+W4vzj>`y zpuRnjc-eHNkvXvI_f{=*FX=OKQzT?bck#2*qoKTHmDe>CDb&3AngA1O)1b}QJ1Tun z_<@yVEM>qG7664Pa@dzL@;DEh`#?yM+M|_fQS<7yv|i*pw)|Z8)9IR+QB7N3v3K(wv4OY*TXnH&X0nQB}?|h2XQeGL^q~N7N zDFa@x0E(UyN7k9g%IFq7Sf+EAfE#K%%#`)!90_)Dmy3Bll&e1vHQyPA87TaF(xbqMpDntVp?;8*$87STop$!EAnGhZ?>mqPJ(X zFsr336p3P{PpZCGn&^LP(JjnBbl_3P3Kcq+m}xVFMVr1zdCPJMDIV_ki#c=vvTwbU z*gKtfic&{<5ozL6Vfpx>o2Tts?3fkhWnJD&^$&+Mh5WGGyO7fG@6WDE`tEe(8<;+q z@Ld~g08XDzF8xtmpIj`#q^(Ty{Hq>t*v`pedHnuj(0%L(%sjkwp%s}wMd!a<*L~9T z9MM@s)Km~ogxlqEhIw5(lc46gCPsSosUFsgGDr8H{mj%OzJz{N#;bQ;KkV+ZWA1(9 zu0PXzyh+C<4OBYQ0v3z~Lr;=C@qmt8===Ov2lJ1=DeLfq*#jgT{YQCuwz?j{&3o_6 zsqp2Z_q-YWJg?C6=!Or|b@(zxTlg$ng2eUQzuC<+o)k<6^9ju_Z*#x+oioZ5T8Z_L zz9^A1h2eFS0O5muq8;LuDKwOv4A9pxmOjgb6L*i!-(0`Ie^d5Fsgspon%X|7 zC{RRXEmYn!5zP9XjG*{pLa)!2;PJB2<-tH@R7+E1cRo=Wz_5Ko8h8bB$QU%t9#vol zAoq?C$~~AsYC|AQQ)>>7BJ@{Cal)ZpqE=gjT+Juf!RD-;U0mbV1ED5PbvFD6M=qj1 zZ{QERT5@(&LQ~1X9xSf&@%r|3`S#ZCE=sWD`D4YQZ`MR`G&s>lN{y2+HqCfvgcw3E z-}Kp(dfGG?V|97kAHQX+OcKCZS`Q%}HD6u*e$~Ki&Vx53&FC!x94xJd4F2l^qQeFO z?&JdmgrdVjroKNJx64C!H&Vncr^w zzR#XI}Dn&o8jB~_YlVM^+#0W(G1LZH5K^|uYT@KSR z^Y5>^*Bc45E1({~EJB(t@4n9gb-eT#s@@7)J^^<_VV`Pm!h7av8XH6^5zO zOcQBhTGr;|MbRsgxCW69w{bl4EW#A~);L?d4*y#j8Ne=Z@fmJP0k4{_cQ~KA|Y#_#BuUiYx8y*za3_6Y}c=GSe7(2|KAfhdzud!Zq&}j)=o4 z7R|&&oX7~e@~HmyOOsCCwy`AR+deNjZ3bf6ijI_*tKP*_5JP3;0d;L_p(c>W1b%sG zJ*$wcO$ng^aW0E(5ldckV9unU7}OB7s?Wx(761?1^&8tA5y0_(ieV>(x-e@}1`lWC z-YH~G$D>#ud!SxK2_Iw{K%92=+{4yb-_XC>ji&j7)1ofp(OGa4jjF;Hd*`6YQL+Jf zffg+6CPc8F@EDPN{Kn96yip;?g@)qgkPo^nVKFqY?8!=h$G$V=<>%5J&iVjwR!7H0 z$@QL|_Q81I;Bnq8-5JyNRv$Y>`sWl{qhq>u+X|)@cMlsG!{*lu?*H`Tp|!uv z9oEPU1jUEj@ueBr}%Y)7Luyi)REaJV>eQ{+uy4uh0ep0){t;OU8D*RZ& zE-Z-&=BrWQLAD^A&qut&4{ZfhqK1ZQB0fACP)=zgx(0(o-`U62EzTkBkG@mXqbjXm z>w`HNeQM?Is&4xq@BB(K;wv5nI6EXas)XXAkUuf}5uSrZLYxRCQPefn-1^#OCd4aO zzF=dQ*CREEyWf@n6h7(uXLNgJIwGp#Xrsj6S<^bzQ7N0B0N{XlT;`=m9Olg<>KL}9 zlp>EKTx-h|%d1Ncqa=wnQEuE;sIO-f#%Bs?g4}&xS?$9MG?n$isHky0caj za8W+B^ERK#&h?(x)7LLpOqApV5F>sqB`sntV%SV>Q1;ax67qs+WcssfFeF3Xk=e4^ zjR2^(%K1oBq%0%Rf!y&WT;lu2Co(rHi|r1_uW)n{<7fGc-c=ft7Z0Q}r4W$o$@tQF#i?jDBwZ8h+=SC}3?anUp3mtRVv9l#H?-UD;HjTF zQ*>|}e=6gDrgI9p%c&4iMUkQa4zziS$bO&i#DI$Wu$7dz7-}XLk%!US^XUIFf2obO zFCTjVEtkvYSKWB;<0C;_B{HHs~ax_48^Cml*mjfBC5*7^HJZiLDir(3k&BerVIZF8zF;0q80eX8c zPN4tc+Dc5DqEAq$Y3B3R&XPZ=AQfFMXv#!RQnGecJONe0H;+!f^h5x0wS<+%;D}MpUbTNUBA}S2n&U59-_5HKr{L^jPsV8B^%NaH|tUr)mq=qCBv_- ziZ1xUp(ZzxUYTCF@C}To;u60?RIfTGS?#JnB8S8@j`TKPkAa)$My+6ziGaBcA@){d z91)%+v2_ba7gNecdj^8*I4#<11l!{XKl6s0zkXfJPxhP+@b+5ev{a>p*W-3*25c&} zmCf{g9mPWVQ$?Sp*4V|lT@~>RR)9iNdN^7KT@>*MU3&v^3e?=NTbG9!h6C|9zO097 zN{Qs6YwR-5$)~ z`b~qs`a1Dbx8P>%V=1XGjBptMf%P~sl1qbHVm1HYpY|-Z^Dar8^HqjIw}xaeRlsYa zJ_@Apy-??`gxPmb`m`0`z`#G7*_C}qiSZe~l2z65tE~IwMw$1|-u&t|z-8SxliH00 zlh1#kuqB56s+E&PWQ7Nz17?c}pN+A@-c^xLqh(j;mS|?>(Pf7(?qd z5q@jkc^nA&!K-}-1P=Ry0yyze0W!+h^iW}7jzC1{?|rEFFWbE^Yu7Y}t?jmP-D$f+ zmqFT7nTl0HL|4jwGm7w@a>9 zKD)V~+g~ysmei$OT5}%$&LK8?ib|8aY|>W3;P+0B;=oD=?1rg+PxKcP(d;OEzq1CKA&y#boc51P^ZJPPS)z5 zAZ)dd2$glGQXFj$`XBBJyl2y-aoBA8121JC9&~|_nY>nkmW>TLi%mWdn-^Jks-Jv| zSR*wij;A3Fcy8KsDjQ15?Z9oOj|Qw2;jgJiq>dxG(2I2RE- z$As!#zSFIskebqU2bnoM^N<4VWD2#>!;saPSsY8OaCCQqkCMdje$C?Sp%V}f2~tG5 z0whMYk6tcaABwu*x)ak@n4sMElGPX1_lmv@bgdI2jPdD|2-<~Jf`L`@>Lj7{<-uLQ zE3S_#3e10q-ra=vaDQ42QUY^@edh>tnTtpBiiDVUk5+Po@%RmuTntOlE29I4MeJI?;`7;{3e4Qst#i-RH6s;>e(Sc+ubF2_gwf5Qi%P!aa89fx6^{~A*&B4Q zKTF|Kx^NkiWx=RDhe<{PWXMQ;2)=SC=yZC&mh?T&CvFVz?5cW~ritRjG2?I0Av_cI z)=s!@MXpXbarYm>Kj0wOxl=eFMgSMc?62U#2gM^li@wKPK9^;;0_h7B>F>0>I3P`{ zr^ygPYp~WVm?Qbp6O3*O2)(`y)x>%ZXtztz zMAcwKDr=TCMY!S-MJ8|2MJCVNUBI0BkJV6?(!~W!_dC{TS=eh}t#X+2D>Kp&)ZN~q zvg!ogxUXu^y(P*;Q+y_rDoGeSCYxkaGPldDDx)k;ocJvvGO#1YKoQLHUf2h_pjm&1 zqh&!_KFH03FcJvSdfgUYMp=5EpigZ*8}7N_W%Ms^WSQ4hH`9>3061OEcxmf~TcYn5_oHtscWn zo5!ayj<_fZ)vHu3!A!7M;4y1QIr8YGy$P2qDD_4+T8^=^dB6uNsz|D>p~4pF3Nrb6 zcpRK*($<~JUqOya#M1=#IhOZ zG)W+rJS-x(6EoVz)P zsSo>JtnChdj9^);su%SkFG~_7JPM zEDz3gk2T7Y%x>1tWyia|op(ilEzvAujW?Xwlw>J6d7yEi8E zv30riR|a_MM%ZZX&n!qm0{2agq(s?x9E@=*tyT$nND+{Djpm7Rsy!+c$j+wqMwTOF zZL8BQ|I`<^bGW)5apO{lh(Asqen?_U`$_n0-Ob~Yd%^89oEe%9yGumQ_8Be+l2k+n zCxT%s?bMpv|AdWP7M1LQwLm|x+igA~;+iK-*+tClF&ueX_V}>=4gvZ01xpubQWXD_ zi?Un>&3=$fu)dgk-Z;0Ll}HK5_YM->l^Czrd0^cJ))(DwL2g3aZuza7ga9^|mT_70 z))}A}r1#-(9cxtn<9jGRwOB4hb9kK@YCgjfOM-90I$8@l=H^`K$cyhe2mTM|FY9vW znH~h)I<_aa#V1xmhk?Ng@$Jw-s%a!$BI4Us+Df+?J&gKAF-M`v}j`OWKP3>6`X`tEmhe#y*(Xm$_^Ybbs=%;L7h zp7q^C*qM}Krqsinq|WolR99>_!GL#Z71Hhz|IwQQv<>Ds09B?Je(lhI1(FInO8mc} zl$RyKCUmfku+Cd^8s0|t+e}5g7M{ZPJQH=UB3(~U&(w#Bz#@DTDHy>_UaS~AtN>4O zJ-I#U@R($fgupHebcpuEBX`SZ>kN!rW$#9>s{^3`86ZRQRtYTY)hiFm_9wU3c`SC8 z-5M%g)h}3Pt|wyj#F%}pGC@VL`9&>9P+_UbudCkS%y2w&*o})hBplrB*@Z?gel5q+ z%|*59(sR9GMk3xME}wd%&k?7~J)OL`rK#4d-haC7uaU8-L@?$K6(r<0e<;y83rK&` z3Q!1rD9WkcB8WBQ|WT|$u^lkr0UL4WH4EQTJyk@5gzHb18cOte4w zS`fLv8q;PvAZyY;*Go3Qw1~5#gP0D0ERla6M6#{; zr1l?bR}Nh+OC7)4bfAs(0ZD(axaw6j9v`^jh5>*Eo&$dAnt?c|Y*ckEORIiJXfGcM zEo`bmIq6rJm`XhkXR-^3d8^RTK2;nmVetHfUNugJG(4XLOu>HJA;0EWb~?&|0abr6 zxqVp@p=b3MN^|~?djPe!=eex(u!x>RYFAj|*T$cTi*Sd3Bme7Pri1tkK9N`KtRmXf zZYNBNtik97ct1R^vamQBfo9ZUR@k*LhIg8OR9d_{iv#t)LQV91^5}K5u{eyxwOFoU zHMVq$C>tfa@uNDW^_>EmO~WYQd(@!nKmAvSSIb&hPO|}g-3985t?|R&WZXvxS}Kt2i^eRe>WHb_;-K5cM4=@AN1>E&1c$k!w4O*oscx(f=<1K6l#8Exi)U(ZiZ zdr#YTP6?m1e1dOKysUjQ^>-MR={OuD00g6+(a^cvcmn#A_%Fh3Of%(qP5nvjS1=(> z|Ld8{u%(J}%2SY~+$4pjy{()5HN2MYUjg1X9umxOMFFPdM+IwOVEs4Z(olynvT%G) zt9|#VR}%O2@f6=+6uvbZv{3U)l;C{tuc zZ{K$rut=eS%3_~fQv^@$HV6#9)K9>|0qD$EV2$G^XUNBLM|5-ZmFF!KV)$4l^KVj@ zZ4fI}Knv*K%zPqK77}B-h_V{66VrmoZP2>@^euu8Rc}#qwRwt5uEBWcJJE5*5rT2t zA4Jpx`QQ~1Sh_n_a9x%Il!t1&B~J6p54zxAJx`REov${jeuL8h8x-z=?qwMAmPK5i z_*ES)BW(NZluu#Bmn1-NUKQip_X&_WzJy~J`WYxEJQ&Gu7DD< z&F9urE;}8S{x4{yB zaq~1Zrz%8)<`prSQv$eu5@1RY2WLu=waPTrn`WK%;G5(jt^FeM;gOdvXQjYhax~_> z{bS_`;t#$RYMu-;_Dd&o+LD<5Afg6v{NK?0d8dD5ohAN?QoocETBj?y{MB)jQ%UQ}#t3j&iL!qr@#6JEajR3@^k5wgLfI9S9dT2^f`2wd z%I#Q*@Ctk@w=(u)@QC}yBvUP&fFRR-uYKJ){Wp3&$s(o~W7OzgsUIPx0|ph2L1(r*_Pa@T@mcH^JxBjh09#fgo|W#gG7}|)k&uD1iZxb0 z@|Y)W79SKj9sS&EhmTD;uI#)FE6VwQ*YAr&foK$RI5H8_ripb$^=;U%gWbrrk4!5P zXDcyscEZoSH~n6VJu8$^6LE6)>+=o#Q-~*jmob^@191+Ot1w454e3)WMliLtY6~^w zW|n#R@~{5K#P+(w+XC%(+UcOrk|yzkEes=!qW%imu6>zjdb!B#`efaliKtN}_c!Jp zfyZa`n+Nx8;*AquvMT2;c8fnYszdDA*0(R`bsof1W<#O{v%O!1IO4WZe=>XBu_D%d zOwWDaEtX%@B>4V%f1+dKqcXT>m2!|&?}(GK8e&R=&w?V`*Vj)sCetWp9lr@@{xe6a zE)JL&;p}OnOO}Nw?vFyoccXT*z*?r}E8{uPtd;4<(hmX;d$rqJhEF}I+kD+m(ke;J z7Cm$W*CSdcD=RYEBhedg>tuT{PHqwCdDP*NkHv4rvQTXkzEn*Mb0oJz&+WfWIOS4@ zzpPJ|e%a-PIwOaOC7uQcHQ-q(SE(e@fj+7oC@34wzaBNaP;cw&gm{Z8yYX?V(lIv5 zKbg*zo1m5aGA4^lwJ|bAU=j3*d8S{vp!~fLFcK8s6%Ng55_qW_d*3R%e=34aDZPfD z&Le39j|ahp6E7B0*9OVdeMNrTErFatiE+=Z!XZ^tv0y%zZKXRTBuPyP&C{5(H?t)S zKV24_-TKpOmCPzU&by8R1Q5HY^@IDoeDA9MbgizgQ*F1Er~HVmvSU>vx}pZVQ&tr| zOtZl8vfY2#L<)gZ=ba&wG~EI*Vd?}lRMCf+!b5CDz$8~be-HKMo5omk$w7p4`Mym*IR8WiTz4^kKcUo^8Hkcsu14u z`Pkg`#-Y^A%CqJ0O@UF|caAulf68@(zhqp~YjzInh7qSN7Ov%Aj(Qz%{3zW|xubJ- ztNE_u_MO7Q_585r;xD?e=Er}@U1G@BKW5v$UM((eByhH2p!^g9W}99OD8VV@7d{#H zv)Eam+^K(5>-Ot~U!R$Um3prQmM)7DyK=iM%vy>BRX4#aH7*oCMmz07YB(EL!^%F7?CA#>zXqiYDhS;e?LYPTf(bte6B ztrfvDXYG*T;ExK-w?Knt{jNv)>KMk*sM^ngZ-WiUN;=0Ev^GIDMs=AyLg2V@3R z7ugNc45;4!RPxvzoT}3NCMeK$7j#q3r_xV(@t@OPRyoKBzHJ#IepkDsm$EJRxL)A* zf{_GQYttu^OXr$jHQn}zs$Eh|s|Z!r?Yi+bS-bi+PE*lH zo|6ztu6$r_?|B~S#m>imI!kQP9`6X426uHRri!wGcK;J;`%sFM(D#*Le~W*t2uH`Q z(HEO9-c_`mhA@4QhbW+tgtt9Pzx=_*3Kh~TB$SKmU4yx-Ay&)n%PZPKg#rD4H{%Ke zdMY@rf5EAFfqtrf?Vmk&N(_d-<=bvfOdPrYwY*;5%j@O6@O#Qj7LJTk-x3LN+dEKy+X z>~U8j3Ql`exr1jR>+S4nEy+4c2f{-Q!3_9)yY758tLGg7k^=nt<6h$YE$ltA+13S<}uOg#XHe6 zZHKdNsAnMQ_RIuB;mdoZ%RWpandzLR-BnjN2j@lkBbBd+?i ze*!5mC}!Qj(Q!rTu`KrRRqp22c=hF6<^v&iCDB`n7mHl;vdclcer%;{;=kA(PwdGG zdX#BWoC!leBC4);^J^tPkPbIe<)~nYb6R3u{HvC!NOQa?DC^Q`|_@ zcz;rk`a!4rSLAS>_=b@g?Yab4%=J3Cc7pRv8?_rHMl_aK*HSPU%0pG2Fyhef_biA!aW|-(( z*RIdG&Lmk(=(nk28Q1k1Oa$8Oa-phG%Mc6dT3>JIylcMMIc{&FsBYBD^n@#~>C?HG z*1&FpYVvXOU@~r2(BUa+KZv;tZ15#RewooEM0LFb>guQN;Z0EBFMFMZ=-m$a3;gVD z)2EBD4+*=6ZF?+)P`z@DOT;azK0Q4p4>NfwDR#Pd;no|{q_qB!zk1O8QojE;>zhPu z1Q=1z^0MYHo1*``H3ex|bW-Zy==5J4fE2;g6sq6YcXMYK5i|S^9(OSw#v!3^!EB<% zZF~J~CleS`V-peStyf*I%1^R88D;+8{{qN6-t!@gTARDg^w2`uSzFZbPQ!)q^oC}m zPo8VOQxq2BaIN`pAVFGu8!{p3}(+iZ`f4ck2ygVpEZMQW38nLpj3NQx+&sAkb8`}P3- zc>N*k6AG?r}bfO6_vccTuKX+*- z7W4Q#2``P0jIHYs)F>uG#AM#I6W2)!Nu2nD5{CRV_PmkDS2ditmbd#pggqEgAo%5oC?|CP zGa0CV)wA*ko!xC7pZYkqo{10CN_e00FX5SjWkI3?@XG}}bze!(&+k2$C-C`6temSk z_YyYpB^wh3woo`B zrMSTd4T?(X-jh`FeO76C(3xsOm9s2BP_b%ospg^!#*2*o9N;tf4(X9$qc_d(()yz5 zDk@1}u_Xd+86vy5RBs?LQCuYKCGPS;E4uFOi@V%1JTK&|eRf~lp$AV#;*#O}iRI2=i3rFL8{ zA^ptDZ0l6k-mq=hUJ0x$Y@J>UNfz~I5l63H(`~*v;qX`Z{zwsQQD-!wp0D&hyB8&Z z7$R07gIKGJ^%AvQ{4KM0edM39iFRx=P^6`!<1(s0t|JbB2tXs_B_IH9#ajH0C=-n+ z`nz`fKMBKLlf?2AC+|83M+0rqR%uhNGD;uKA6jOjp7YDe^4%0fRB<^bcjlS2KF~F; zu09wh1x0&4pG&76M;x8$u`b134t=dEPBn6PV|X29<#T4F1mxGF*HOgiWU8tN@cguI z_F@o+XL7FJztR63wC|j4x_DANzcX94r7Iz-O2x$({&qd*mdLG=-Rv)uZ}UlMR+F&q zU}=lkfb0p1>1Ho){o$@}mSKIV;h*$AND7~Dl)QzpFBlSM99Kx+F7GsVK5xcR? z_4Q(Z%cgk8ST}U;;=!LwyZVu^S$>B-Waeik%wzcKTIqeX=0FP(TGQ=nxi=dsS5BYF zl@?}NT!Y!Iyos^@v7XWXA{_bV~1lxz7gC?xuXxy0_?GaN!AhRRM5>)^t%&ODd;@HN5L{MD3 zc>i2keQZVm#?NrDwbfd}_<*5^U&w0zv~n-y8=GGN-!=_`FU^cM8oVCWRFxw?BM^YD zi=Vxz4q|jwPTg+?q7_XI)-S@gQkh>w0ZUB}a{^ z_i;`Y(~fvpI!vmW*A^|P7(6+@C4UeL2WATf{P1?H5rk`5{TL zcf!CgP6Mi{MvjZS)rfo7JLDZK7M7ANd$3`{j9baD*7{#Zu-33fOYUzjvtKzR2)_T1I1s7fe&z|=)QkX;=`zX8!Byw-veM#yr;|wjO^II>!B*B z0+w%;0(=*G3V@88t!}~zx)&do(uF=073Yeh*fEhZb3Vn>t!m(9p~Y_FdV3IgR)9eT z)~e9xpI%2deTWyHlXA(7srrfc_`7ACm!R>SoIgkuF8 z!wkOhrixFy9y@)GdxAntd!!7@=L_tFD2T5OdSUO)I%yj02le`qeQ=yKq$g^h)NG;# za(0J@#VBi^5YI|QI=rq{KlxwGabZJ0dKmfWDROkcM}lUN$@DV`K7fU?8CP2H23QPi zG?YF*=Vn=kTK*#Y_{AQN&oLju|0#E=fx%YVh>S{puu&K$b;BN*jIo@VYhqPiJPzzM>#kxoy0vW9i;ne2_BIG0zyRFp<3M(iY(%*M_>q0ulV2K}Tg zkG{EWKS{i%4DUuHi%DVKy%e+Q!~Uf`>>F6NgD{{I8~nO4!VgOvtFOc7(O)X`|7n*f zxBa4CJ-v9fUUH+`7sPVvpM_C*udZ@OTGTzx56QM5y~OlrZc&w9=)B?nmd@keRn+^= zvm~4sa5987LFDnU{(N|N zJAR8H@}p1fC+H(yTI4n#%~TbImMpuqYn9cQ<0QQ%=PzZItLkC*ef9WJUvfITKWh#D zc#__8`4am9%#NslIUw+<82#SR8AYG|woLfBg#!-&dqq}@P>|I0%lbdy0lSMmNe+}o zj0zZuFr6Wb?Y{Qy-S=|r`bdrDmhnmvkRnkdn`YCleU>Q$=je}LGhh>_QAj6aa_0Oc z%Swsmui;IRx7bN*=AAS@5yW&Y2hy;3&|HAiA8}!HT6!Z!RVn~MZg`RmI6&%#tBZDx zfD+y@Z~NWlk*4l13vmt3AK2wP!fQlnBbECL>?p)F?T)<`w&QN>cP_V>r7UTcsTaaP zTOb$f!P@zf$6>890NVKbIkG8rE?9!Y97sMSZjfF?A zYR8lp`LMoz~O?iaZN;gcX;LC-%Ia*R%A&SLx!YIf29?P+=XAAojK8!^OU*@?R&DK!#G_lsn!#;S375uZ&B0HH1|BO0R90$U>qs zSvHv>H~mAgNCcjo-e+;RjY6B9NCbQrZ|BHjTkehaU<9CSkdd>Vl*ifA2LNOP&R2Qdy3k3-TQ+ zbq=#vI43x`s=%~cGyN&y4Y!FxhwgDe@i6uv8^BLL&3z*SO=D0aLjih?gY4-9uWp5or)H+v~w6n5X#F-I52z=Z_p4JB(;M| zeaVFhuR2|3UD2MzVc~^nSoD2(dD#uL_1PdnIxeA{V5n`#3xf1Zx@4lw(DsQ&H$h zw#%3O<1173hjg2_nhKi!d1ej=h7y`hVjCNB6|HTnx>SWuCE-kgTnfT+YGX4_Lun({ zDv2`>d3vrS)tTf7ps_vvh!Cx^e1BFuWnEAh0(7fkNk|-3oU|iRWdsC6U)?Raft~HN z;^$U}vZK5O8|LV$>6X5T(uYkblv{zwPxnQBh(BQ5tA~J!vGiAMYP^_ki~pkIxDfOZ zUJDwq%O~WueeV6%uN<54&u*c&E4y431cklBNrb06zGOOy4XNT~JS-q(s6@)F@ovbe ze`fial(O4(-su%6@@1+V0MsdLLMyE8;)nou(7}czU(5ASaZYDT(kUZ0L(&g$nF^n9 z9-Pi`ZZLX&)^*M6As4_2Mmc9S7OT)F8KkL2NJ)KJcnCuWU=Wy402A&45#Q9Id~BBH z0cY*xlv!uXzKrXLH!xQu(OtJvEj|0-DmRj1vjFz{c*I4$Pe(+_V|^b~S!0xm{8lq= zZv)@NlcyL3Xdz+*|L137F7y6L-2VsrKw=q^S>F6i%<{Fr8zk06$Ay-(!L$fY@7mcng!2}L0t zgi|KxfB63Xtk_Q8#ZPipQ@!zgjdpEIbK_?q17Hoi4Eiyun$hrc>T(7pOLVLQE=lgGwA+A308p& z7@=09(|$>eLy5gLe{*|3b(M;1n;C^~v?o88jYib48eR4$QGsBFzd}3QuwO^_XE(=B zq+hMi0UFC|dB{LCwch7;zYT=NK})O%sgi0k#yV;My@24^B1+CuZmYOh0^b)5Ba_)) zC%i#_Iev&nsu%I|1N5=MVc#PrlunKAs&hY|3s5;@}`>sB>}gzxuB zB=2vrRyB3uiyW(hkDUNe1@&(b`;>ZvGgw|@s{zVC#_`HXIN_^J@Etb zA7A+F?ot37T{<-vTy8h&b3e+WKHE1oh;pUQrN4yRRrx?mT_9jRa2i4l1fUnLW^Cbl z!I1>VzyFe?VELWWhM?@?t-YPZkD-Qjo@bC2(o#ZtZmr{KZsdFWItV`rs$gp{724@C zL8K5}E0+DHcWcL^{BGei4>@J-3%a#$y6;I}=upc};-NDv-z#kPX26ylOpH)Ov1uU{ zkLj6oiH6l_s+B~_z;|Jc2oi?naS7#3H63~~lWj4rUnd=fCnKdkik<@R&kch9q##G{ z4u!%=rlM~Yp3jk*t8}1B`Sv6<%Z^}~1e@aq zg|JQ`QO2pSjAm-g*?IrNc$^~sIrNBo2$m|Sxanr?Mfs>2@Auu49 zGXlsS<9XS1&8h(dD*Hl&5HBDG!^pJ*lkau_Ur+7`7z;rcs$hT4we?3bT=7Fe<>{5( z2m2(c+hUz2BTHM8dCe*Z3XX&Av;b~a=$6EF>&^E8%nyxO@m_n!q&XD^A{SRjRZQ0L~qDeC=j&0$j6=LNIz@`ni^>ch|sv}^6 zlm>?28yPl@WmDPR?Y-A9X{U9Dv_IsbXJnzKCjkRksLOg#42uG2mE_acbTQ4)J|1V>%U@K(FP3AYhL0U zdeOCPN1qLv!|#c=p!_+%VNV(GHt`RuLRV^vz<5tt-r)yOK**kUWPspVAf|}ZL{LS= z@k(@@!P&W!>wwe`x{+GrFSWhHov7hu?{KuuT%kl#WO@*WX$i_@retlhQBj++SVNCx z5$78LxP>Z=^aJ)D280r_jj=zFfMJFXCIe^B{~V@d1rl_F(qo&AB4bC-vYL>x2jSKX zpuTG-6kgp3e^T&+dtV*i6a~)v@n?n*MffN59y}<0djUX zt27R+SE#hp8bzc#;rk$jw3r4)Q@eI$*`_)=Pvge8@8|8>H3X)<9YX6cXa=ii#Le;(qKm@%0-7$>2ShnYc`j#zJ7gu_FE^?uAkL|H)UIH#gPu^40!6^J=^ zr`}iwa^!4tzW~vOMZAaKF>*8A{^8m$i(VK)>?=#l`xrVe>wseSvM_aF zATNkY>kM_P3?1kE`uIq#mvr-wuTgUH0N<&JhF=(E9%^NS*HLm!4GZ4_XI zL=R5tlG5Mk_1rPfg)sk^llFuKPMPBhuU|L5q#yP_mzxp1o&pAzi-X31sgFpIHn@($ z_>=`AB5(8tP6p2zS5VEvH5J$M` z_much3>S7t3Yo`Yx!>83-hW9LYzDKP?mKdkD#QAK8*M((sx{eBQdrR<^3ZhFP81+& zBnJMUefQyNBji~$5d88Wfw1Lv59aJN9t2!pABLg;ewJ#LXL-10;QcJl+Y4Mtngb)k6JZlCf)3uD_u)J3sYyN;NN5hNbg$%W!i-GK%e&!Us)2IExWSss$YG(hm3kJ-h%yD z>8q^n$+4I(_y_mbT{du4P%h1j3oSpjhY97{+IZ`aA4ug!vNJ6*p?<2H(2w+GD3j$I z1TUXGyNzdf>_yB3grP~FZUs<2Quw;eEi*7s(-MiIkQ%@J^+WGdQvYSUN+TRiD-xto zJ=OUU+kxGYc!HCLNbCvR4lGTp~#L;DFzGd-#gJe*xf(P3hDQz|y)?b9mwU3WUVnpcqXM<@w%r-k*Wr^gzAv)8T^sqA=Ye z!7qy&exJmAcAt~CwS#@yNmjr8*T*!A6w4~E*ibaLRs0CFo(;R3=ODhDt6zWNodmo0 zXx&bT$6&+5c>a|WJ)F4G-^GjY0H#*tY=UNyYr_q5fsrcjk(c^~e*7Lf`!Jd`)p412 zn|^*hV= zFI4UbwA%X@smDd$cQOiMC%jfitTxTb+#`9`G=2rJDfK!E=5ra|So>lc{X1$~w28i+ z4p&cTGwZ#5VueiXS9O8#;RR$yg7tL9!^)Sz&pZYIzlSh}0}V{LxL$Cu%B4U5_}k}- zm~|CsD<076x@<>m=6w6N?WaThIBP`!u{-;WF)xc=2otx*lwf|5+MkdJePjh(B z9SH+%cHGCMAXNxB{_3^otDWdsV7Ob6n{0 z+&!(;iaHOX__5z_$Qk{%xYV%Ig@7iokGBwR`3642ZP#H#v9QGbWl8<|MS*=@qO@Uj z6+SZ_v9`1paUe5tFN~v(b#J3a_Lx0+;r9giZIx-A5TxdbG>xi#AZ5_z1V}B^n)sxT zz49}eK7EWb6wR!6-qQOrHQHkUvshvq%=G2d&@(#XM*Am1;WbnJ{X_!a{ZkphD$^TQ z=Iskb&}=lBm(RHiwJoGg`*NiQ6#RB$T#LF+>#ef;Jne&MxKPX!#r`&TVEFsp2jnNx>dClzpcPy&G&13a_<0qaR3i+k212~hoQ z8nMk{JP-t04I{GW5gUBqcJW-jSMrlw}>p)ptx?WKuCUV77taMiV zHok9V=6yv+Uts@fMY&A}amC=!Yj}eL@=e%XJ#%?agkt1jWF+10{(E9mHLDa>Ll7Vj zG=3cp%ljIB-6pC}6&`xJ*6WCP|IlglLWJ^?yviI8Ve)?V_i4%n;olzny62_`-|IGi z^=}p_O>Z8M;c4|RExu70E7ePW(HWVS&E$+LL6xSQgB`QfMQJ|4pCTFowA39p5P-|$ zUtM_H2HnP8_RoS~Vwk(FhbG zH41licj%=0a;Ln2STFBvU}Ne&O&%8bYKj!h1FA#sNM`232fX|U3QPp#3C?mN2;hE9 z;)!@5ixSPl<89^7gwhHc2YAX1KJK$#*3`KOMIQ253q7-*RJ5k)zp9GBO|Ga~X*^}US5oN@aG&waHV%vi~r{t^`ptTxb zL}q1W8S7*>7oWwvgV4uFLZ(@k`R*=LO_|Gu`prs~!WQXj-NLIa^2(7IHg>BG^N zc|i{-^=&Cek9dkJFQys|sjG9i>LLz|;yCv{^1i%c*h>8zF91kLvS9HBQi~ZU!JL`B zK8N+U0fr1*6??Ium)AF!6tc1eGhXIYL6IRT7rmKp7+>?%5Pa6zC5)KY$ycF0ZJ`G5nEQDG100U-jLkH8^UE4g6wq?sg%pP=-$&G#bcN`^?w3a6 z((s$6eRKcSEIslW-kk5Qi|5Mg-(xdLF}PxxVh$PuO}#aR6pW1kV4Af!Bqh*btXNNZ z>-4(IUl+L4dw+3LcpGut=qB45O+W)Q5?*zZ2A6rJcg`qkSvWA!j^r2mqKuCm6`Py? z@^T#Ux04HemPGd!Hs7NkZdVn1}8_j`o?)*OKZGS!`ff)gF zG?v-lj$wWNWCcw2Mg2o18D~1?3_b0XzdiKBNkYSDpcv@&kp0POmweJE2ZkIQ3B!a! zIgIoE+Xv?;34kyo^QYjZk+tEqZvq^#QG(OzX4~X+KtsoQoddTWUR(yo8R+ObEF1j<-syWOb>)JQ&Zbdu(sctU%Mt zW&YR0{ttY2TTXYZ?~WNU&cES1Z2q(7SrWDh``!J(JM+Nk$!hu&Y;(7E`ZNKTe0w+% zJc?Qnw2B+%UR}0;cB0Rufa(7-3FF}?629@LgTiEC&2uyL6NxexOp?AKT^aAx3gi(W zao>r>MPw0eQ3>IV02uLsC@>yK_epX6GRg4{NEL2wPPF9=*L2RV3yyK8DhuEK>rmmV z`&Q~#c`lgR&93TdOCja|ewOXmPNRh7!&dMT(1ett#iDr8HZW~VqWW@7fe9B6;7S+? zbC`d4@MEau&mKlOPKd>*10q0c{~^baw6!a*w^sY#0Xim{oOsiXiDOhbG&kl3c$$n1 zMRrD83&QucDSEcV*7LIp8VTA@F<%qe+_c`L;6on(>SjAU^}5c9!BCffT>$VQhe=)z z8(=Ej{5>jhmjB3{xDfj2R@VmHQ!CqjlO4KnuOmvHy3K#po$yp_V;p_MKjh1`(rzj6 zHW956k1yvntz{_g?Xbs`avK(IjlTnsu%htO;D7 z?J#x^EzuvVn&NA=!MEj7cwe5A-Z$Zk2LBZH$~%E* zf`((xH0?`}hs|HA%mtwfOEsZJxxrennkTYcwP#FKO5%Lpc^JXhSpV|ZH$Wr;`}`_( zIP==gd3LYyVtwD|*ZJGi{7~x8{=^bGVqu0RJ`n_BZH9+}kz%-4ZRsImi@rx%=ZEKs zcPnUXo6hbJV>fH;@1|bAHIe0ijYI*&kdT|HkDS$9No9 zCHo=*HWb~U+Dtzxr+Esao}6@|;Pf+E$ay0$kQp#s{wlw+7aIKbMdf`OqhoG*;Tco0 zjrP}VQG#Y2cJuqoJg&5({)S(BA}q9T1lGeWRyu=Je|)I!6a+aj!IP^1({)ZYe&x6w zt3a)Dq^TB+A7CdB0-}#z2Ur$W&h3YVw8==!xONy$uQmDWh-@15iEOt!q2m&?ZLA|w z8loSb(0}7y6Xu0?M5Uf4>VZGluB`wMf2oh;m)ghxVda>3m}4%V)r^0nVQ5V6f3>*) z0&VN!N0~GC^P}vj$`EDMZEmVV;N&RISY2C;$0;2(<{Lt&PKzqRByQdiEHGAbwtbS zPj`Da5%U6k1oEtVzI}QNw;!hT6F+~|@=c@$C4NtO@=xgP?|5MyZAyuCzcvq4rdAv@C06%gZ`9%I);R6UGiGJobfux+<0DLS&|MSG4UH z_~o{^^9>ixMg~mY!-@Fai{xaE4^;qy9iZN15Gbn5ZqHWf>Jc5Rv6(#n8`1NcCsdmG zab*dSXVPaE?)wCalD;$ivF%@nB#7D`@YG04p6ed9m}4iJW|pfVMLE<-c{=-8$e?cH zUdU#mCj4gb zZKA^b9p*9S(}8@tw~1RNPHr7tQr;P+-)D8|sq=*o)G%RGqt> zzP5yf`pVxb)I51D_G~Xp^GNK zVI6sAX)a9s)e{8N3?35YA6aQTXuyszK3ah~CemzA&CII#8F&F#KN41~8I^&_%}6MCNb{W87qAF`zj_Y^szhb> z3p3}KbOxotY|(lD=;)`fYE_*{S}x;f^SW#)SU&5X#o|-R|trpa|L5PS5aa0 zTHw8%SDSVtU4?vyrhnq+^@dgFS)|(y{~(4j%3UEiO-rBM9%`)8(dh33pMLiuurNY# z#10AsQ7%*0Cu_DSAU}P;X(JwA64~Q_^R%d_zSm^6Aux?Pn70PM>9EvLeOX z&w9c)pGmcL22;MO3C_B>=NC0RJpMp8?#ZUf=GWRvy z6RHq3B}=MGVg?9@iKFBpsvnkVh3{Vpp=`CcD=u~@ql{my|6?3ssi3mCOPnjI&E}VC zc@X+Yl>;;DNo0W0`0th!X{?luDhOC{E8N=?!w}K1{V=)+1={m(f`Oc|N=07>}3;z{-(A zm{JL=j?Sro5iecmE2-pWlRf(r%|HEQ7kgwQ9+kt=NBhtQI7OwcZ#3%$Uf%^r2nhjY zoQ08MfC%_X{O9~WcirMZMhn#z^ux4Erx-tf-6bHD)9eH&^L>^jvAd^9A^DCDs?0;k zkm7LE*KjP6`2d17MrQaaLqd_Rka}J$csvUec#hw78<=s(hyR>065~YCVCA9+#Q+; za(*L0IEw!r5P|@-;x33L$Lv9 zcuN8YG&g{<(SeJG18~(b!5yywSqQiLAX0;---;}mF5&b4lg|T?LwKREa{9YX_-zL@ZE?Zqi@HxK^2KO1>0LATu{te=T zprmHtY)bDVfxI1S}KBE7V zznP7KQ8HekWU#W6mw`dr-boV}pMQR==&5=Q5T=_q091jfc;R*jX#&=MQ%~@E@9^?`$v48ks<>(fI(F6L(5ppKy|$HWng*bKOb(4|cMUB&z$#ob#XV z5-mg)gmFIybZf=znm3ZPyUO^GJfxt0kmHjaTZ|sthsxXw&}Y)fOUSg=JhRSR^UjZ- zhqqb}Wsyw4zdnj6@#BAJa#-PdI4_dgafFXh85DsEQ_cT+5)XpZq$fZlBA_9UsE9r6 zEFec5?uqN@QhJ^IzwZrwl-5J`CmVPv{(YDTqEqWR^dI;5hXc~cxP%B3v&~s0`Ct89 z@S`i~a^c%V^N81dDT*ItFS*&IN;@O$EgzX0e7x&}TD=!zS}hTpezBLS>mdX(5< z)8DEI(-o_D)c-UX@dA1MuJ*yc>Hf4|`*B2S_O>w*-tbUwtiu`;W(Ud{HTty@(&x(T(F&;M zJ=?H>6`B7nf-90e8V`WSVp|0oEKB-P2M{}4ZDawzvM&a!y>`Y#jCsD%T_l``@ah(I2nJs~Q|%uSKu@k!m~*8B*IoA{*TgtF<(5sHCGG;n@NE%~Xt(G$^&<87u;}Na zx-8cq0g`uA(&RBFo=-4Y1GUZ<``Zw{xL4jfHkZw~%~wvtGueszcXt)_QwH8g!; z%s&3kSa~R$dO$-%L-)c@_hi7&>{6L_M>OZFkUQu;{sL_bUMStNrt{{&O(Wn~*zPOk zB>dnfszb29NSTf2pqIs68k|p-UrSrxgLHqi?3N-UFa!LHy9n1)=s>`yS+J{MEzS@ zNlfGtpma7kG&LR3JE@wB%rFA*h~~KitlO=IP)ZjN6dQLM6qsry zHkB#cyNh#n`)}bCrN1My*;k)^@>e4gJ`LJK?2)Pwp?4Tl4)4FA0(tvY+#1jOUM)xw zlMz4x-f@g^+yKUN`?Vu)|AwujArnM~Pa@y*Q9S8eS(u{-S%(Z5=R~pRl5ZGDjdqH% zC8rW&{##wOpU_oTIG4WXMk4&%2t1;lWcW5&!yxmOT*!hBcKyTqEcNoO+R2;Q?Yj+W z1-Y4?59fijz4(MIDwGe4-baYf08UCs;r|YefD-Md2ST;=cxwpgW=tR76-dQVAhn^= zG9Wk5lQk%jIR@KNU!UMp6@BfU;r+;y4VQ)D2!Il9HX%yW-9nOzV+m$YKzVaO`B8S7t z$!S2Mz`xw>V(RjE`0>bQp<0y&h~Y=M#jpy!#=dE>`=e_AjSZq6u!Dy1xJf~-7|0F! zPR9|n`e_7D2DIV2H(CESQ}hA>U>n|6`%z?YKEA~)BOVY%y=jPV zT=44R!L?J)736X#csn|lfBJ)o8ixaZclguWgrGO<`TN2FMfO}7;5}d+BlK0yTSH3* z4!=;5rOh85&2|x=46hkNaz?)U8&=bcfh=N_#8BNpZ2v$aVBo;sk^*X`v;4-LU;D>! zM*h12MxXIQy)SfAqE4;jY)wgnppazZkdNNVVF;(PLf^qK$FgY9+VFyBKE7UC|f z`R|?&egV11K3s$rJ6!GvoeW=jV*!-e(wA;x(2=d0E_e_%0x--0o8#~m^H1%AH5Z^B zn!TNPn927*bvaf0pt}zhK0o^V@WlGwwKo(*nQ|Q~4_;>~-8y20`HP>@UJa)3nEnGG z5Hwhs|FcmFG16ZVNb5hL`2Gc1{zWIMM{_OiKewV!hCi}U!VuE?s9wU-QbZ!)+Y^tS zGzp5OSi5iq6hmEr$w}&9DFgoB+i*`q`8TBi^MVS{SKEb8Aw%@K7@XCo(De2A`6%mf&a2#~y1N)+kJLD$1HCP!22)(U}xo2|j?WRzt(11j8Z_*v;P$R+Ug*Gy3VxV4K; zGGUGabnW*`Z}~`ydXL-l9e=GC$pY#z|63vy>E*m=$=j}iWP{sRTh0%H54`t>2xYH% zsk+M&u&pNgMCM@3e)Xc?jBWX-TIR_cQ1Z!RW7!B zBjZX=+^3}?SE)B+$EP+0oi1Fp5blDT?*}nsP>filqXH{ms zxU<$hetC`u)Wi+x|EKL-`y^#aQX+sDYIa{M;V%LqLrOk~lR>u0Q!+pyQSU4zY`?E^ z|5@)C)w6G_=i5YYC5SE_u(7hDNYr}uKT|@DSqF%S++lTIbIk^$a>{~0IH8KNFEy%+ zW#$&!ynpgNJh>6uR~?2c)ZMW+h0OKu231(7L_vETPaR+(P)Zy%0~yGm>E9?@@x!Jy z3PYgS}Q@b}x}E#F27@F+j}0=&Ql4gES&f8acMrPAVlVs9$97`FR))R5wI zc&}KFI1UIewh>3PkhnB7u zS3AT8_*|nexznG|Z*DU0c!K@jsI4J)5#DyNi#|e#`l1Vv1`1)*NVcy0LZ``aL0n8B zecupJ(rhq3u8bW0NIRhKYq$v1li+jp*4hfAd&wxYDE8vn1TQ7S@bTM|I2Ob z8vMOIxA7&_j{AKmD+O@EyXT`|dElt0pED^@IV0m)RPBUs*5jW60>>w1!@_G3aBKzG z_f(KfAPBk}-jQtR*Sroq!*3rbQ_m27e+YdzQjUb<_*k8vc_C)y!@cj5E>NxUhPu&g z@Z2<~esU`)ih+4opWe+K7sbN9n*9@n>#@n3*o z?xoROgDuvhq>jJ;Ve{6i<3roQNfgo5^4Q4(|GNExO2Dr7GjgA2zWuKp_K)K0R(6lv z!l$!zW-+T6mb3gQaAFviTQi{|*t%>{(mhTdy+y;Re4qT@kccy#{b z&zWy~kLO@>*WPj2k#H)|7L&gAJ37DmHQAme#@m;(Y8Nu^`D5vf8sZFW#+lA2!HK=( zJ)#hO6JD*`o~&c*&46d}g=Qj@SsoB5ikC z^1V8E+&<-OzuS_C`p5<<(A6fB`LXT(!kV^0_~hL6PpW4={l%|#xgdh?5EIk~lu8{D z2hiyhv3Yxij_#$Wu>P@7SYsl`-~3;}Ktx{34_NL^Kwin&=?!HDv3elQDbcU*qyYpN z(#yw~f1vFGK-t%CC-qa-4FYHbA^h>bag-I&*qaxwn?Qv|idE$<>1H|Gr6JtUu(he2$eg!N z@HTF@dG1)*y;4fxe)4_ZkpaBHH9hXp9p4|gLrRQyuevRd@gSS}JhRnWqrvm|U@>qM z=yl7RQROTKwQtzP3!zUF)_6Ld#NGA6v~2{J9Dd`h6{%+XsU#qGLh%`fB1Hc?wfayK zN`H4BpDp)npVQuu$DVW1qsBS&AJ2eP%6Qw>;k{)Z$8%HL=Q4(a$Ng2_vHw&vA!1L+9zc8vaX2GtqJ{L-;gvF0IR$em zMQ8@{Qp3+3Quk)TJ$?I<8KmwzD*7#(q<@Mc`dchngW}cRG14(Z6K7{T|LhFXwhqUQ;BET;cYqPcAcMgt6M$V9$(?jHo@Sud$an$U&5F zZ1QNh^ztt)E*d#Ij;<43oSKKnd+WNr$_r}+s_O_x6DZSB10*5Q{ourqq>mTl| zx4y^(cy+9;t@R=*j>3_dmm_m)$k$#937V(sllby&5)Xex^UD-|m|q<(jEd#@DV(of zAd7sSdmS*zUDqJ9|K%O2J2OfdUiK{{b{PCy)pi<;hp~7v1CQj&4-10 zgO<3dqhYH1#-Fa}Q{pjql5>>P6gZH21zLfxZ4$SK4T@7b!|`nWF9b*84Bq8&Eht;9 z*P72x&NUCZ7*@B$`FtE=hz5b}S`|c6Ey+j@D1ZibjJaRlR;{cxAWv z?Nqa>QqV*H-*zzaPvpLMHt~nl(x6?vrPpR?zn7~wow?oj*1TKmx4j71>$hvtC$DLD zUrz0^tiP0792U&dxJxNv@r}Elsjn^aSLUu=9#mD{&9n8|ayIL$!H3s>%KEvbchBFW z%cd?VU83mGF#Dar9*s~w&AnmQRQIOvR+uWsuZ?+|a=TzApXO@q^(r%8=}iv#wCnFq z=K9}JbqU@k99Q%j-}NNk+qLCP)jXfmOO|)@?mHcnynd6({mJisP1_}u7k)|eYHXWK z63eQ)E$ufFi!3CWUY2gw%e>omCv}qEX66aH-k&35f9`Q@Us|NPetVqe8=dX*VxJdn ze`q7b=Dn(UA(2sf&g)cOmQFhNJ#<-aMELJZbA#@to>25@kbW<)&!X01 z%NMJt>1ST)tyX)h@?`DxhbgCHr>S4wv}WC&Nw-!{+Z7$2D}74QAcXTvip=M0%Tp_N zor=k`)t|ra^ySr-+(|R9mB(E=`MX#y(wSw)$!iymzB;^c*>%&^*7HxTnRga=soSZT zdDl+9s;r!v8hk6POtzBaig4pRp7eWF(<8gufvNHPu6xs-=e{;mnHzJyGKE+8L0j}; z@%8-e^UCL5HhMiR>sD3Rve&yVZ#{Q1*CO8c+qSr^Z#CN;)(X5>tGG5yUw3<+CfhaL z%bP;hZ?jvgJU67BWyiy74_)6r)_nSxttxn0`0?HE^5(uydHVgP+HE$V?Lv)Leti43 zWA|;f-RqX``95>)^P-fw!Vi{3KNsII-*5f){gdxqd%gVdB1sOBNe=nEW%;i~g_P8J w!5uhoe-Jcg1nPN%MiEAtgE$;km@@t6ukO)1^!cY^83Pb_y85}Sb4q9e0FIsP9{>OV literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 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_