diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4f81299 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} diff --git a/analysis_options.yaml b/analysis_options.yaml index bc80d75..f34c54a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,3 @@ - # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # @@ -13,10 +12,18 @@ include: package:flutter_lints/flutter.yaml analyzer: exclude: - "lib/models/old" + errors: + must_be_immutable: ignore + dead_null_aware_expression: ignore + unused_local_variable: ignore + unnecessary_null_comparison: ignore + use_key_in_widget_constructors: ignore linter: rules: - use_key_in_widget_constructors: false - + library_private_types_in_public_api: false + use_build_context_synchronously: false + file_names: false + avoid_print: false # Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options \ No newline at end of file +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore index bc2100d..b68efd4 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,3 +5,4 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +/build diff --git a/android/app/build.gradle b/android/app/build.gradle index 07a5e91..4e8172b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -32,21 +32,24 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 31 - + compileSdk 35 + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = '17' + } sourceSets { main.java.srcDirs += 'src/main/kotlin' } - lintOptions { - disable 'InvalidPackage' - } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "org.scottylabs.thdapp" - minSdkVersion 20 - targetSdkVersion 31 + minSdkVersion 33 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -66,6 +69,10 @@ android { signingConfig signingConfigs.release } } + namespace 'org.scottylabs.thdapp' + lint { + disable 'InvalidPackage' + } } flutter { @@ -73,7 +80,7 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..72d2a95 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,14 @@ +-dontwarn androidx.window.extensions.WindowExtensions +-dontwarn androidx.window.extensions.WindowExtensionsProvider +-dontwarn androidx.window.extensions.area.ExtensionWindowAreaPresentation +-dontwarn androidx.window.extensions.layout.DisplayFeature +-dontwarn androidx.window.extensions.layout.FoldingFeature +-dontwarn androidx.window.extensions.layout.WindowLayoutComponent +-dontwarn androidx.window.extensions.layout.WindowLayoutInfo +-dontwarn androidx.window.sidecar.SidecarDeviceState +-dontwarn androidx.window.sidecar.SidecarDisplayFeature +-dontwarn androidx.window.sidecar.SidecarInterface$SidecarCallback +-dontwarn androidx.window.sidecar.SidecarInterface +-dontwarn androidx.window.sidecar.SidecarProvider +-dontwarn androidx.window.sidecar.SidecarWindowLayoutInfo + diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index d109f0a..f880684 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a1fc219..354dc60 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/android/build.gradle b/android/build.gradle index 0848bc5..fddd179 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.6.0' + ext.kotlin_version = '1.8.22' repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.0' + classpath 'com.android.tools.build:gradle:8.8.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -14,8 +14,44 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } + // This code is where all the magic happens and fixes the error. + subprojects { + afterEvaluate { project -> + if (project.hasProperty('android')) { + project.android { + if (namespace == null) { + namespace project.group + } + } + } + } + } + // This code is where all the magic happens and fixes the error. +} + +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} + +// add These lines from here. in android/build.gradle (NOT android/app/build.gradle) +subprojects { + afterEvaluate { project -> + if (project.plugins.hasPlugin("com.android.application") || + project.plugins.hasPlugin("com.android.library")) { + project.android { + compileSdkVersion 35 + buildToolsVersion "35.0.0" + } + } + } +} + + +// to here +subprojects { + project.evaluationDependsOn(':app') } rootProject.buildDir = '../build' @@ -26,6 +62,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index 38c8d45..c32a4ef 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -2,3 +2,6 @@ org.gradle.jvmargs=-Xmx1536M android.enableR8=true android.useAndroidX=true android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 2b22d05..b70e5a5 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ +#Sun Jan 26 19:39:12 EST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip -networkTimeout=10000 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/integration_tests/login_test.dart b/integration_tests/login_test.dart index 5f15f77..d085e16 100644 --- a/integration_tests/login_test.dart +++ b/integration_tests/login_test.dart @@ -8,22 +8,21 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('end-to-end login test', () { - testWidgets('enter login details', - (tester) async { - app.main(); - await tester.pumpAndSettle(); + testWidgets('enter login details', (tester) async { + app.main(); + await tester.pumpAndSettle(); - // Enter text into text fields - Finder textFieldFinder = find.byType(TextField); - await tester.enterText(textFieldFinder.first, "login details"); - await tester.enterText(textFieldFinder.last, "password details"); + // Enter text into text fields + Finder textFieldFinder = find.byType(TextField); + await tester.enterText(textFieldFinder.first, "login details"); + await tester.enterText(textFieldFinder.last, "password details"); - // Trigger a frame. - await tester.pumpAndSettle(); + // Trigger a frame. + await tester.pumpAndSettle(); - // Check for text in textfields - expect(find.text('login details'), findsOneWidget); - expect(find.text('password details'), findsOneWidget); - }); + // Check for text in textfields + expect(find.text('login details'), findsOneWidget); + expect(find.text('password details'), findsOneWidget); + }); }); -} \ No newline at end of file +} diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d2..8c6e561 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/ios/Podfile b/ios/Podfile index 88359b2..279576f 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7e8fd90..fa0b511 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,28 +4,29 @@ PODS: - Flutter - image_cropper (0.0.4): - Flutter - - TOCropViewController (~> 2.6.1) - - image_picker (0.0.1): + - TOCropViewController (~> 2.7.4) + - image_picker_ios (0.0.1): - Flutter - integration_test (0.0.1): - Flutter - share (0.0.1): - Flutter - - shared_preferences (0.0.1): + - shared_preferences_foundation (0.0.1): - Flutter - - TOCropViewController (2.6.1) - - url_launcher (0.0.1): + - FlutterMacOS + - TOCropViewController (2.7.4) + - url_launcher_ios (0.0.1): - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - flutter_smart_scan (from `.symlinks/plugins/flutter_smart_scan/ios`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) - - image_picker (from `.symlinks/plugins/image_picker/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - share (from `.symlinks/plugins/share/ios`) - - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: @@ -38,28 +39,28 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_smart_scan/ios" image_cropper: :path: ".symlinks/plugins/image_cropper/ios" - image_picker: - :path: ".symlinks/plugins/image_picker/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" share: :path: ".symlinks/plugins/share/ios" - shared_preferences: - :path: ".symlinks/plugins/shared_preferences/ios" - url_launcher: - :path: ".symlinks/plugins/url_launcher/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a - flutter_smart_scan: 2f7b350e28147cd3387810415be505ecc027bfb9 - image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98 - image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f - integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 - share: 0b2c3e82132f5888bccca3351c504d0003b3b410 - shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 - url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_smart_scan: 7bbde55ca151f1b89e74359ea174e09ea2431fa6 + image_cropper: 5f162dcf988100dc1513f9c6b7eb42cd6fbf9156 + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + share: a34936589f3090d59481bcdc5c30cc9dd47c75f6 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d -PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 +PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 -COCOAPODS: 1.11.3 +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 859cbfa..525b21f 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -165,7 +165,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -211,10 +211,12 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -247,22 +249,22 @@ "${BUILT_PRODUCTS_DIR}/TOCropViewController/TOCropViewController.framework", "${BUILT_PRODUCTS_DIR}/flutter_smart_scan/flutter_smart_scan.framework", "${BUILT_PRODUCTS_DIR}/image_cropper/image_cropper.framework", - "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", + "${BUILT_PRODUCTS_DIR}/image_picker_ios/image_picker_ios.framework", "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", "${BUILT_PRODUCTS_DIR}/share/share.framework", - "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", - "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", + "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", + "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TOCropViewController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_smart_scan.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_cropper.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -366,7 +368,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -384,7 +386,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 4Y39FMA838; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -394,13 +396,16 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "TartanHacks Dashboard"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 3.0.1; + MARKETING_VERSION = 4.0.7; PRODUCT_BUNDLE_IDENTIFIER = org.scottylabs.slapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -457,7 +462,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -506,7 +511,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -525,7 +530,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 4Y39FMA838; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -535,13 +540,16 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "TartanHacks Dashboard"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 3.0.1; + MARKETING_VERSION = 4.0.7; PRODUCT_BUNDLE_IDENTIFIER = org.scottylabs.slapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -561,7 +569,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 4Y39FMA838; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -571,13 +579,16 @@ INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "TartanHacks Dashboard"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 3.0.1; + MARKETING_VERSION = 4.0.7; PRODUCT_BUNDLE_IDENTIFIER = org.scottylabs.slapp; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6..e67b280 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ CFBundlePackageType APPL CFBundleShortVersionString - $(MARKETING_VERSION) + $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion - $(CURRENT_PROJECT_VERSION) + $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS NSCameraUsageDescription @@ -49,5 +49,9 @@ UIViewControllerBasedStatusBarAppearance + UIApplicationSupportsIndirectInputEvents + + ITSAppUsesNonExemptEncryption + diff --git a/lib/api.dart b/lib/api.dart index caa203f..1de9f34 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -17,15 +17,16 @@ import 'models/project.dart'; import 'models/team.dart'; import 'models/discord.dart'; import 'package:http_parser/http_parser.dart'; +import 'models/config.dart'; -SharedPreferences prefs; +late SharedPreferences prefs; -const baseUrl = "https://dev.backend.tartanhacks.com/"; +const baseUrl = "https://backend.tartanhacks.com/"; -Future checkCredentials(String email, String password) async { - String url = baseUrl + "auth/login"; +Future checkCredentials(String email, String password) async { + Uri url = Uri.parse("${baseUrl}auth/login"); Map headers = {"Content-type": "application/json"}; - String json1 = '{"email":"' + email + '","password":"' + password + '"}'; + String json1 = '{"email":"$email","password":"$password"}'; final response = await http.post(url, headers: headers, body: json1); @@ -41,19 +42,19 @@ Future checkCredentials(String email, String password) async { prefs.setBool('admin', loginData.admin); prefs.setBool('judge', loginData.judge); prefs.setString('id', loginData.id); - prefs.setString('company', loginData.company); prefs.setString('status', loginData.status); + prefs.setString('company', loginData.company ?? ""); return loginData; - } else { - return null; } + + return null; } Future resetPassword(String email) async { - String url = baseUrl + "auth/request-reset"; + Uri url = Uri.parse("${baseUrl}auth/request-reset"); Map headers = {"Content-type": "application/json"}; - String json1 = '{"email":"' + email + '"}'; + String json1 = '{"email":"$email"}'; final response = await http.post(url, headers: headers, body: json1); if (response.statusCode == 200) { @@ -64,7 +65,7 @@ Future resetPassword(String email) async { } Future getProfile(String id, String token) async { - String url = baseUrl + "users/" + id + "/profile"; + Uri url = Uri.parse("${baseUrl}users/$id/profile"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -74,16 +75,15 @@ Future getProfile(String id, String token) async { if (response.statusCode == 200) { var data = json.decode(response.body); Profile profile = Profile.fromJson(data); - print(data); // profile.picture = "lib/logos/defaultpfp.PNG"; return profile; } else { - return null; + throw Error(); } } Future checkNameAvailable(String name, String token) async { - String url = baseUrl + "user/name/available"; + Uri url = Uri.parse("${baseUrl}user/name/available"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -96,17 +96,17 @@ Future checkNameAvailable(String name, String token) async { var data = json.decode(response.body); return data; } else { - return null; + throw Error(); } } -Future setDisplayName(String name, String token) async { +Future setDisplayName(String name, String token) async { bool isAvailable = await checkNameAvailable(name, token); if (isAvailable != true) { return isAvailable; } - String url = baseUrl + "user/profile"; + Uri url = Uri.parse("${baseUrl}user/profile"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -118,14 +118,14 @@ Future setDisplayName(String name, String token) async { if (response.statusCode == 200) { return true; } else { - return null; + throw Error(); } } -Future getStudents(String token, {String query}) async { - String url = baseUrl + "participants"; +Future getStudents(String token, {String? query}) async { + Uri url = Uri.parse("${baseUrl}participants"); if (query != null) { - url = url + "?name=" + query; + url = Uri.parse("$url?name=$query"); } Map headers = { "Content-type": "application/json", @@ -142,14 +142,14 @@ Future getStudents(String token, {String query}) async { .toList(); return [ids, profs, teams]; } - return null; + throw Error(); } Future getBookmarkIdsList(String token) async { var bookmarks = {}; List bookmarkIds; List bmParticipantIds; - String url = baseUrl + "bookmarks/participant"; + Uri url = Uri.parse("${baseUrl}bookmarks/participant"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -168,11 +168,11 @@ Future getBookmarkIdsList(String token) async { } return bookmarks; } - return null; + throw Error(); } Future> getParticipantBookmarks(String token) async { - String url = baseUrl + "bookmarks/participant"; + Uri url = Uri.parse("${baseUrl}bookmarks/participant"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -182,13 +182,13 @@ Future> getParticipantBookmarks(String token) async { if (response.statusCode == 200) { List data = json.decode(response.body); data = data.map((bm) => ParticipantBookmark.fromJson(bm)).toList(); - return data; + return data as List; } - return null; + throw Error(); } Future> getProjectBookmarks(String token) async { - String url = baseUrl + "bookmarks/project"; + Uri url = Uri.parse("${baseUrl}bookmarks/project"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -198,13 +198,14 @@ Future> getProjectBookmarks(String token) async { if (response.statusCode == 200) { List data = json.decode(response.body); data = data.map((bm) => ProjectBookmark.fromJson(bm)).toList(); - return data; + return data as List; } - return null; + + throw Error(); } Future deleteBookmark(String token, String id) async { - String url = baseUrl + "bookmark/" + id; + Uri url = Uri.parse("${baseUrl}bookmark/$id"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -213,19 +214,18 @@ Future deleteBookmark(String token, String id) async { } Future addBookmark(String token, String participantId) async { - String url = baseUrl + "bookmark"; + Uri url = Uri.parse("${baseUrl}bookmark"); Map headers = { "Content-type": "application/json", "x-access-token": token }; - String jsonInput = '{"bookmarkType": "PARTICIPANT", "participant":"' + - participantId.toString() + - '", "project":null, "description": "sample description"}'; + String jsonInput = + '{"bookmarkType": "PARTICIPANT", "participant":"$participantId", "project":null, "description": "sample description"}'; final response = await http.post(url, headers: headers, body: jsonInput); if (response.statusCode != 200) { - return null; + throw Error(); } else { Map data = Map.from(json.decode(response.body)); @@ -234,8 +234,8 @@ Future addBookmark(String token, String participantId) async { } } -Future>> getEvents() async { - var url = baseUrl + 'schedule/'; +Future>?> getEvents() async { + var url = Uri.parse("${baseUrl}schedule/"); final response = await http.get(url); if (response.statusCode == 200) { List eventsList; @@ -254,7 +254,7 @@ Future>> getEvents() async { } return [upcomingEvents, pastEvents]; } else { - return null; + throw Error(); } } @@ -270,9 +270,9 @@ Future addEvent( String platformUrl) async { SharedPreferences prefs = await SharedPreferences.getInstance(); - String token = prefs.getString("token"); + String token = prefs.getString("token")!; - String url = baseUrl + "schedule/"; + Uri url = Uri.parse("${baseUrl}schedule"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -282,25 +282,8 @@ Future addEvent( // for (int i = 0; i < essayQuestions.length; i++) { // essayQuestionsAgg += essayQuestions[i] + "\n"; // } - String bodyJson = '{"name":"' + - name + - '","description":"' + - description + - '","startTime":' + - startTime.toString() + - ',"endTime":' + - endTime.toString() + - ',"location":"' + - location + - '","lat":' + - lat.toString() + - ',"lng":' + - lng.toString() + - ',"platform":"' + - platform + - '","platformUrl":"' + - platformUrl + - '"}'; + String bodyJson = + '{"name":"$name","description":"$description","startTime":$startTime,"endTime":$endTime,"location":"$location","lat":$lat,"lng":$lng,"platform":"$platform","platformUrl":"$platformUrl"}'; final response = await http.post(url, headers: headers, body: bodyJson); if (response.statusCode == 200) { @@ -326,9 +309,9 @@ Future editEvent( String platformUrl) async { SharedPreferences prefs = await SharedPreferences.getInstance(); - String token = prefs.getString("token"); + String token = prefs.getString("token")!; - String url = baseUrl + "schedule/" + eventId; + Uri url = Uri.parse("${baseUrl}schedule/$eventId"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -358,9 +341,9 @@ Future editEvent( Future deleteEvent(String eventId) async { SharedPreferences prefs = await SharedPreferences.getInstance(); - String token = prefs.getString("token"); + String token = prefs.getString("token")!; - String url = baseUrl + "schedule/" + eventId; + Uri url = Uri.parse("${baseUrl}schedule/$eventId"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -376,7 +359,7 @@ Future deleteEvent(String eventId) async { // Check In Endpoints Future> getCheckInItems() async { - String url = baseUrl + "check-in"; + Uri url = Uri.parse("${baseUrl}check-in"); Map headers = {"Content-type": "application/json"}; final response = await http.get(url, headers: headers); @@ -394,7 +377,7 @@ Future> getCheckInItems() async { 2 - List */ Future getUserHistory(String userID, String token) async { - String url = baseUrl + "check-in/history/$userID"; + Uri url = Uri.parse("${baseUrl}check-in/history/$userID"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -421,7 +404,7 @@ Future getUserHistory(String userID, String token) async { } Future addCheckInItem(CheckInItemDTO item, String token) async { - String url = baseUrl + "check-in"; + Uri url = Uri.parse("${baseUrl}check-in"); String itemJson = jsonEncode(item); Map headers = { "Content-type": "application/json", @@ -436,7 +419,7 @@ Future addCheckInItem(CheckInItemDTO item, String token) async { Future editCheckInItem( CheckInItemDTO item, String id, String token) async { - String url = baseUrl + "check-in/$id"; + Uri url = Uri.parse("${baseUrl}check-in/$id"); String itemJson = jsonEncode(item); Map headers = { "Content-type": "application/json", @@ -450,7 +433,7 @@ Future editCheckInItem( } Future deleteCheckInItem(String id, String token) async { - String url = baseUrl + "check-in/$id"; + Uri url = Uri.parse("${baseUrl}check-in/$id"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -475,7 +458,7 @@ Future checkInUser(String id, String uid, token) async { if (response.statusCode != 200) { Map error = jsonDecode(response.body); String msg = error['message'].toString(); - if (msg.length<=2) { + if (msg.length <= 2) { msg = "We encountered an error while checking you in."; } throw Exception(msg); @@ -484,11 +467,11 @@ Future checkInUser(String id, String uid, token) async { Future getCurrentUserID() async { SharedPreferences prefs = await SharedPreferences.getInstance(); - return prefs.getString("id"); + return prefs.getString("id")!; } Future> getLeaderboard() async { - String url = baseUrl + "leaderboard"; + Uri url = Uri.parse("${baseUrl}leaderboard"); final response = await http.get(url); @@ -498,12 +481,12 @@ Future> getLeaderboard() async { data.map((json) => LBEntry.fromJson(json)).toList(); return lb; } else { - return null; + throw Error(); } } Future getSelfRank(String token) async { - String url = baseUrl + "leaderboard/rank"; + Uri url = Uri.parse("${baseUrl}leaderboard/rank"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -515,12 +498,12 @@ Future getSelfRank(String token) async { var data = json.decode(response.body); return data; } else { - return null; + throw Error(); } } Future> getPrizes() async { - String url = baseUrl + "prizes"; + Uri url = Uri.parse("${baseUrl}prizes"); final response = await http.get(url); @@ -530,12 +513,12 @@ Future> getPrizes() async { data.map((json) => Prize.fromJson(json)).toList(); return prizes; } else { - return null; + throw Error(); } } -Future getProject(String id, String token) async { - String url = baseUrl + "users/" + id + "/project"; +Future getProject(String id, String token) async { + Uri url = Uri.parse("${baseUrl}users/$id/project"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -552,81 +535,100 @@ Future getProject(String id, String token) async { } } -Future newProject( +Future newProject( BuildContext context, String name, String desc, - String teamId, + String team, String slides, String video, - String ghurl, - bool isVirtual, - String id, - String token) async { - String url = baseUrl + "projects"; + String github, + bool presenting, + String projId, + String token, + {int? tableNumber} + ) async { + Uri url = Uri.parse("${baseUrl}projects/"); Map headers = { "Content-type": "application/json", "x-access-token": token }; - String body = json.encode({ + + Map body = { "name": name, "description": desc, - "team": teamId, + "team": team, "slides": slides, "video": video, - "url": ghurl, - "presentingVirtually": isVirtual - }); - final response = await http.post(url, headers: headers, body: body); + "url": github, + "presentingVirtually": presenting, + }; + if (tableNumber != null) { + body["tableNumber"] = tableNumber; + } + + final response = await http.post( + url, + headers: headers, + body: json.encode(body) + ); if (response.statusCode == 200) { - var data = json.decode(response.body); - Project project = Project.fromJson(data); - return project; + return true; } else { - return Future.error("Error"); + errorDialog(context, "Error", json.decode(response.body)['message']); + return false; } } -Future editProject( +Future editProject( BuildContext context, String name, String desc, String slides, String video, - String ghurl, - bool isVirtual, - String id, - String token) async { - String url = baseUrl + "projects/" + id; + String github, + bool presenting, + String projId, + String token, + {int? tableNumber} + ) async { + Uri url = Uri.parse("${baseUrl}projects/$projId"); Map headers = { "Content-type": "application/json", "x-access-token": token }; - String body = json.encode({ + + Map body = { "name": name, "description": desc, "slides": slides, "video": video, - "url": ghurl, - "presentingVirtually": isVirtual - }); - final response = await http.patch(url, headers: headers, body: body); + "url": github, + "presentingVirtually": presenting, + }; + if (tableNumber != null) { + body["tableNumber"] = tableNumber; + } + + final response = await http.patch( + url, + headers: headers, + body: json.encode(body) + ); if (response.statusCode == 200) { - var data = json.decode(response.body); - Project project = Project.fromJson(data); - return project; + return true; } else { - return Future.error("Error"); - //errorDialog(context, "Error", json.decode(response.body)['message']); + errorDialog(context, "Error", json.decode(response.body)['message']); + return false; } } Future enterPrize( BuildContext context, String projId, String prizeId, String token) async { - String url = - baseUrl + "projects/prizes/enter/" + projId + "?prizeID=" + prizeId; + Uri url = + Uri.parse("${baseUrl}projects/prizes/enter/$projId?prizeID=$prizeId"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -642,8 +644,8 @@ Future enterPrize( } } -Future getTeamById(String id, String token) async { - String url = baseUrl + "users/" + id + "/team"; +Future getTeamById(String id, String token) async { + Uri url = Uri.parse("${baseUrl}users/$id/team"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -660,7 +662,7 @@ Future getTeamById(String id, String token) async { } Future getDiscordInfo(String token) async { - String url = baseUrl + 'user/verification'; + Uri url = Uri.parse('${baseUrl}user/verification'); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -672,29 +674,29 @@ Future getDiscordInfo(String token) async { DiscordInfo discord = DiscordInfo.fromJson(data); return discord; } else { - return null; + throw Error(); } } Future uploadProfilePic(io.File profilePicFile, String token) async { - String url = baseUrl + "user/profile-picture"; - var uri = Uri.parse(url); + Uri uri = Uri.parse("${baseUrl}user/profile-picture"); var request = http.MultipartRequest("POST", uri); - request.files.add(http.MultipartFile.fromBytes("file", profilePicFile.readAsBytesSync(), filename: "Photo.jpg", contentType: MediaType("image", "jpg"))); + request.files.add(http.MultipartFile.fromBytes( + "file", profilePicFile.readAsBytesSync(), + filename: "Photo.jpg", contentType: MediaType("image", "jpg"))); request.headers['x-access-token'] = token; var response = await request.send(); if (response.statusCode == 200) { return true; - } - else { + } else { return false; } } Future deleteProfilePic(String token) async { - String url = baseUrl + "user/profile-picture"; + Uri url = Uri.parse("${baseUrl}user/profile-picture"); final http.Response response = await http.delete( - Uri.parse(url), + url, headers: { 'Content-Type': 'application/json', "x-access-token": token @@ -702,10 +704,64 @@ Future deleteProfilePic(String token) async { ); if (response.statusCode == 200) { return true; - } - else { + } else { return false; } } +// Get all projects (admin only) +Future> getAllProjects(String token) async { + Uri url = Uri.parse("${baseUrl}projects/all"); + Map headers = { + "Content-type": "application/json", + "x-access-token": token + }; + + final response = await http.get(url, headers: headers); + + if (response.statusCode == 200) { + List projectsJson = json.decode(response.body); + return projectsJson.map((json) => Project.fromJson(json)).toList(); + } else { + throw Exception('Failed to load projects'); + } +} + +// Update project table number (admin only) +Future updateProjectTableNumber(String projectId, int tableNumber, String token) async { + Uri url = Uri.parse("${baseUrl}projects/$projectId/table"); + Map headers = { + "Content-type": "application/json", + "x-access-token": token + }; + + final response = await http.patch( + url, + headers: headers, + body: json.encode({"tableNumber": tableNumber}) + ); + + return response.statusCode == 200; +} + +Future getExpoConfig(String token) async { + Uri url = Uri.parse("${baseUrl}config/expo"); + Map headers = { + "Content-type": "application/json", + "x-access-token": token + }; + + final response = await http.get(url, headers: headers); + + if (response.statusCode == 200) { + return ExpoConfig.fromJson(json.decode(response.body)); + } else { + throw Exception('Failed to load expo configuration'); + } +} +// ignore: unused_element +bool _canSubmitTableNumber(ExpoConfig config) { + final now = DateTime.now(); + return now.isBefore(config.expoStartTime); +} diff --git a/lib/components/DefaultPage.dart b/lib/components/DefaultPage.dart index ee5ab19..fcdd05f 100644 --- a/lib/components/DefaultPage.dart +++ b/lib/components/DefaultPage.dart @@ -9,7 +9,12 @@ class DefaultPage extends StatelessWidget { final bool backflag; final bool isSponsor; - const DefaultPage({this.child, this.reverse=true, this.backflag=false, this.isSponsor=false}); + const DefaultPage({ + required this.child, + this.reverse=true, + this.backflag=false, + this.isSponsor=false + }); @override Widget build(BuildContext context) { @@ -36,7 +41,8 @@ class DefaultPage extends StatelessWidget { painter: CurvedTop( color1: Theme.of(context).colorScheme.primary, color2: Theme.of(context).colorScheme.secondary, - reverse: reverse + reverse: reverse, + padding: mqData.padding.top ) ), ] diff --git a/lib/components/ErrorDialog.dart b/lib/components/ErrorDialog.dart index 7d972e1..83e4733 100644 --- a/lib/components/ErrorDialog.dart +++ b/lib/components/ErrorDialog.dart @@ -6,13 +6,13 @@ void errorDialog(context, String title, String response) { builder: (BuildContext context) { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text(title, style: Theme.of(context).textTheme.headline1), - content: Text(response, style: Theme.of(context).textTheme.bodyText2), + title: Text(title, style: Theme.of(context).textTheme.displayLarge), + content: Text(response, style: Theme.of(context).textTheme.bodyMedium), actions: [ TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -22,4 +22,4 @@ void errorDialog(context, String title, String response) { ); }, ); -} \ No newline at end of file +} diff --git a/lib/components/background_shapes/CurvedBottom.dart b/lib/components/background_shapes/CurvedBottom.dart index a536b19..0c132fa 100644 --- a/lib/components/background_shapes/CurvedBottom.dart +++ b/lib/components/background_shapes/CurvedBottom.dart @@ -3,7 +3,10 @@ import 'package:flutter/material.dart'; class CurvedBottom extends CustomPainter { Color color1; Color color2; - CurvedBottom({this.color1, this.color2}); + CurvedBottom({ + required this.color1, + required this.color2 + }); @override void paint(Canvas canvas, Size size) { var paint = Paint() diff --git a/lib/components/background_shapes/CurvedTop.dart b/lib/components/background_shapes/CurvedTop.dart index 92a1566..4fb04d8 100644 --- a/lib/components/background_shapes/CurvedTop.dart +++ b/lib/components/background_shapes/CurvedTop.dart @@ -4,7 +4,13 @@ class CurvedTop extends CustomPainter { Color color1; Color color2; bool reverse; - CurvedTop({this.color1, this.color2, this.reverse = false}); + double padding; + CurvedTop({ + required this.color1, + required this.color2, + this.reverse = false, + this.padding = 0 + }); @override void paint(Canvas canvas, Size size) { var paint = Paint() @@ -17,8 +23,8 @@ class CurvedTop extends CustomPainter { ).createShader(!reverse ? Rect.fromLTRB(0, 0, size.width, size.height) : Rect.fromLTRB(size.width, size.height, 0, 0)); var path = Path(); double curveHeight = size.height * (0.3); - path.moveTo(0, curveHeight); - path.cubicTo(.03*size.width, .17*curveHeight, .97*size.width, .83*curveHeight, size.width, 0); + path.moveTo(0, curveHeight - padding * 0.5); + path.cubicTo(.03*size.width, .17*curveHeight - padding * 0.5, .97*size.width, .83*curveHeight, size.width, -padding); path.lineTo(size.width, size.height); path.lineTo(0, size.height); canvas.drawPath(path, paint); diff --git a/lib/components/buttons/GradBox.dart b/lib/components/buttons/GradBox.dart index 044520e..6a51fde 100644 --- a/lib/components/buttons/GradBox.dart +++ b/lib/components/buttons/GradBox.dart @@ -1,16 +1,25 @@ import 'package:flutter/material.dart'; class GradBox extends StatelessWidget{ - final double width; - final double height; + final double? width; + final double? height; final double curvature; final Widget child; final bool reverse; - final EdgeInsets padding; - final Function onTap; - final Alignment alignment; - const GradBox({this.width, this.height, this.child, this.reverse=false, - this.padding, this.onTap, this.alignment, this.curvature=25}); + final EdgeInsets? padding; + final void Function()? onTap; + final Alignment? alignment; + + const GradBox({ + this.width, + this.height, + required this.child, + this.reverse=false, + this.padding, + this.onTap, + this.alignment, + this.curvature=25 + }); @override Widget build(BuildContext context) { diff --git a/lib/components/buttons/SolidButton.dart b/lib/components/buttons/SolidButton.dart index 005eed4..96ae8d0 100644 --- a/lib/components/buttons/SolidButton.dart +++ b/lib/components/buttons/SolidButton.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; -class SolidButton extends StatelessWidget{ - String text; - Function onPressed; - Widget child; - Color color; - Color textColor; +class SolidButton extends StatelessWidget { + String? text; + void Function()? onPressed; + Widget? child; + Color? color; + Color? textColor; - SolidButton({this.text, this.onPressed, this.child, this.color, this.textColor}); + SolidButton( + {this.text, this.onPressed, this.child, + this.color, + this.textColor}); @override Widget build(BuildContext context) { @@ -24,21 +27,22 @@ class SolidButton extends StatelessWidget{ return ElevatedButton( onPressed: onPressed, style: ButtonStyle( - foregroundColor: MaterialStateProperty.all(color), - backgroundColor: MaterialStateProperty.all(color), - shadowColor: MaterialStateProperty.all(Theme.of(context).colorScheme.shadow), - shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))), - elevation: MaterialStateProperty.all(5) - ), - child: child ?? Text(text, - style: TextStyle( - fontSize:16.0, - fontWeight: FontWeight.w600, - color: textColor - ), - overflow: TextOverflow.fade, - softWrap: false, - ) - ); + foregroundColor: WidgetStateProperty.all(color), + backgroundColor: WidgetStateProperty.all(color), + shadowColor: + WidgetStateProperty.all(Theme.of(context).colorScheme.shadow), + shape: WidgetStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10))), + elevation: WidgetStateProperty.all(5)), + child: child ?? + Text( + text!, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: textColor), + overflow: TextOverflow.fade, + softWrap: false, + )); } -} \ No newline at end of file +} diff --git a/lib/components/buttons/SolidSquareButton.dart b/lib/components/buttons/SolidSquareButton.dart index 9d29a70..eb47fce 100644 --- a/lib/components/buttons/SolidSquareButton.dart +++ b/lib/components/buttons/SolidSquareButton.dart @@ -1,23 +1,27 @@ import 'package:flutter/material.dart'; -class SolidSquareButton extends StatelessWidget{ +class SolidSquareButton extends StatelessWidget { final String image; - final Function onPressed; + final void Function() onPressed; - const SolidSquareButton({this.image, this.onPressed}); + const SolidSquareButton({required this.image, required this.onPressed}); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onPressed, style: ButtonStyle( - foregroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.primary), - backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.primary), - shadowColor: MaterialStateProperty.all(Theme.of(context).colorScheme.secondaryVariant), - shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))), - fixedSize: MaterialStateProperty.all(const Size.square(10)), - elevation: MaterialStateProperty.all(5) - ), + foregroundColor: + WidgetStateProperty.all(Theme.of(context).colorScheme.primary), + backgroundColor: + WidgetStateProperty.all(Theme.of(context).colorScheme.primary), + shadowColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.secondaryContainer), + shape: WidgetStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))), + fixedSize: WidgetStateProperty.all(const Size.square(10)), + elevation: WidgetStateProperty.all(5)), + child: const SizedBox(), ); } -} \ No newline at end of file +} diff --git a/lib/components/loading/ListRefreshable.dart b/lib/components/loading/ListRefreshable.dart index 8ef0d0b..5d019ed 100644 --- a/lib/components/loading/ListRefreshable.dart +++ b/lib/components/loading/ListRefreshable.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class ListRefreshable extends StatelessWidget { Widget child; - ListRefreshable({this.child}); + ListRefreshable({ required this.child }); @override Widget build(BuildContext context) { diff --git a/lib/components/loading/LoadingOverlay.dart b/lib/components/loading/LoadingOverlay.dart index ec12fc5..e58c399 100644 --- a/lib/components/loading/LoadingOverlay.dart +++ b/lib/components/loading/LoadingOverlay.dart @@ -1,25 +1,21 @@ import 'package:flutter/material.dart'; import 'package:thdapp/components/loading/WhiteOverlayLight.dart'; -OverlayEntry LoadingOverlay(BuildContext context) { +OverlayEntry loadingOverlay(BuildContext context) { final screenSize = MediaQuery.of(context).size; return OverlayEntry( builder: (context) => Positioned( - child: Stack( + child: Stack( alignment: Alignment.center, children: [ - CustomPaint( - size: screenSize, - painter: WhiteOverlayLight() - ), + CustomPaint(size: screenSize, painter: WhiteOverlayLight()), Container( width: screenSize.width, height: screenSize.height, alignment: Alignment.center, - child: CircularProgressIndicator(color: Theme.of(context).colorScheme.error,) - ) + child: CircularProgressIndicator( + color: Theme.of(context).colorScheme.error, + )) ], - ) - ) - ); -} \ No newline at end of file + ))); +} diff --git a/lib/components/loading/LoadingScreen.dart b/lib/components/loading/LoadingScreen.dart index bbb05fe..188f22b 100644 --- a/lib/components/loading/LoadingScreen.dart +++ b/lib/components/loading/LoadingScreen.dart @@ -12,27 +12,26 @@ class LoadingScreen extends StatelessWidget { height: screenHeight, width: screenWidth, alignment: Alignment.center, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children:[ - Container( - height: screenHeight*0.35, - width: screenWidth, - alignment: Alignment.topCenter, - padding: const EdgeInsets.fromLTRB(0, 20, 0, 10), - child: Image.asset("lib/logos/thLogoDark.png") - ), - Text("Tartanhacks", - style: Theme.of(context).textTheme.headline1, - ), - Text("by Scottylabs", - style: Theme.of(context).textTheme.bodyText2, - ), - const SizedBox(height: 25), - CircularProgressIndicator(color: Theme.of(context).colorScheme.primary,) - ] - ) - ) - ); + child: + Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Container( + height: screenHeight * 0.35, + width: screenWidth, + alignment: Alignment.topCenter, + padding: const EdgeInsets.fromLTRB(0, 20, 0, 10), + child: Image.asset("lib/logos/thLogoDark.png")), + Text( + "Tartanhacks", + style: Theme.of(context).textTheme.displayLarge, + ), + Text( + "by Scottylabs", + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 25), + CircularProgressIndicator( + color: Theme.of(context).colorScheme.primary, + ) + ]))); } -} \ No newline at end of file +} diff --git a/lib/components/menu/MenuButton.dart b/lib/components/menu/MenuButton.dart index 865dcb8..d4c226f 100644 --- a/lib/components/menu/MenuButton.dart +++ b/lib/components/menu/MenuButton.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:thdapp/components/buttons/GradBox.dart'; class MenuButton extends StatelessWidget { - final Function onTap; - final IconData icon; + final void Function()? onTap; + final IconData? icon; const MenuButton({this.onTap, this.icon}); @override @@ -15,11 +15,11 @@ class MenuButton extends StatelessWidget { width: 55, height: 55, padding: const EdgeInsets.all(0), + onTap: onTap, child: Icon(icon ?? Icons.menu, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 35 - ), - onTap: onTap + ) ) ); } diff --git a/lib/components/menu/MenuChoice.dart b/lib/components/menu/MenuChoice.dart index fd02d4c..1c6a5e2 100644 --- a/lib/components/menu/MenuChoice.dart +++ b/lib/components/menu/MenuChoice.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; class MenuChoice extends StatelessWidget { final IconData icon; final String text; - final Function onTap; - const MenuChoice({this.icon, this.text, this.onTap}); + final void Function()? onTap; + const MenuChoice({ required this.icon, required this.text, required this.onTap}); @override Widget build(BuildContext context) { final mqData = MediaQuery.of(context); @@ -19,16 +19,16 @@ class MenuChoice extends StatelessWidget { onPressed: onTap, elevation: 2.0, fillColor: Theme.of(context).colorScheme.errorContainer, + padding: const EdgeInsets.all(12), + shape: const CircleBorder(), child: Icon( icon, size: 40.0, color: Theme.of(context).colorScheme.onErrorContainer, ), - padding: const EdgeInsets.all(12), - shape: const CircleBorder(), ), Text(text, - style: Theme.of(context).textTheme.bodyText1.copyWith( + style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.errorContainer ), ) diff --git a/lib/components/menu/MenuFunctions.dart b/lib/components/menu/MenuFunctions.dart index 7ad5e07..a3389b1 100644 --- a/lib/components/menu/MenuFunctions.dart +++ b/lib/components/menu/MenuFunctions.dart @@ -7,7 +7,7 @@ import 'package:thdapp/providers/user_info_provider.dart'; void logOut(entry, context) async { var prefs = await SharedPreferences.getInstance(); - String theme = prefs.getString("theme"); + String theme = prefs.getString("theme") ?? "dark"; await prefs.clear(); prefs.setString("theme", theme); entry.remove(); diff --git a/lib/components/menu/MenuOverlay.dart b/lib/components/menu/MenuOverlay.dart index 0001dd4..48f577f 100644 --- a/lib/components/menu/MenuOverlay.dart +++ b/lib/components/menu/MenuOverlay.dart @@ -17,9 +17,9 @@ import './MenuButton.dart'; OverlayEntry menuOverlay(BuildContext context) { final mqData = MediaQuery.of(context); final screenWidth = mqData.size.width; - var _themeProvider = Provider.of(context, listen: false); + var themeProvider = Provider.of(context, listen: false); bool hasTeam = Provider.of(context, listen: false).hasTeam; - OverlayEntry entry; + late OverlayEntry entry; entry = OverlayEntry( builder: (context) => GestureDetector( @@ -85,12 +85,12 @@ OverlayEntry menuOverlay(BuildContext context) { (route) => false); }, ), - _themeProvider.getTheme == lightTheme + themeProvider.getTheme == lightTheme ? MenuChoice( icon: Icons.mode_night, text: "Dark", onTap: () { - _themeProvider.setTheme(darkTheme); + themeProvider.setTheme(darkTheme); setThemePref("dark", entry, context); }, ) @@ -98,7 +98,7 @@ OverlayEntry menuOverlay(BuildContext context) { icon: Icons.wb_sunny, text: "Light", onTap: () { - _themeProvider.setTheme(lightTheme); + themeProvider.setTheme(lightTheme); setThemePref("light", entry, context); }, ), diff --git a/lib/components/menu/SponsorMenuOverlay.dart b/lib/components/menu/SponsorMenuOverlay.dart index fc76287..f361bbc 100644 --- a/lib/components/menu/SponsorMenuOverlay.dart +++ b/lib/components/menu/SponsorMenuOverlay.dart @@ -13,8 +13,8 @@ OverlayEntry sponsorMenuOverlay(BuildContext context) { final mqData = MediaQuery.of(context); final screenWidth = mqData.size.width; - var _themeProvider = Provider.of(context, listen: false); - OverlayEntry entry; + var themeProvider = Provider.of(context, listen: false); + late OverlayEntry entry; entry = OverlayEntry( builder: (context) => GestureDetector( @@ -22,109 +22,96 @@ OverlayEntry sponsorMenuOverlay(BuildContext context) { onTap: () { entry.remove(); }, - child: Stack( - alignment: Alignment.topRight, - children:[ - CustomPaint( - size: mqData.size, - painter: WhiteOverlay() - ), - Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.end, - children:[ - Container( - width: screenWidth/4, - alignment: Alignment.topRight, - padding: const EdgeInsets.fromLTRB(0, 25, 17, 0), - child: MenuButton( - onTap: () { - entry.remove(); - }, - icon: Icons.close, - ) - ), - ] - ), - const SizedBox(height: 10), - Container( - alignment: Alignment.topRight, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children:[ - MenuChoice( - icon: Icons.person, - text: "Home", - onTap: () { - entry.remove(); - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: (context) => - Sponsors(), - ), (route) => route.isFirst - ); - }, + child: Stack(alignment: Alignment.topRight, children: [ + CustomPaint(size: mqData.size, painter: WhiteOverlay()), + Column(children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + width: screenWidth / 4, + alignment: Alignment.topRight, + padding: const EdgeInsets.fromLTRB(0, 25, 17, 0), + child: MenuButton( + onTap: () { + entry.remove(); + }, + icon: Icons.close, + )), + ]), + const SizedBox(height: 10), + Container( + alignment: Alignment.topRight, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MenuChoice( + icon: Icons.person, + text: "Home", + onTap: () { + entry.remove(); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => Sponsors(), ), - MenuChoice( - icon: Icons.schedule, - text: "Schedule", - onTap: () { - entry.remove(); - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: (context) => - EventsHomeScreen(), - ), (route) => route.isFirst - ); - }, + (route) => route.isFirst); + }, + ), + MenuChoice( + icon: Icons.schedule, + text: "Schedule", + onTap: () { + entry.remove(); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => EventsHomeScreen(), ), - MenuChoice( - icon: Icons.bookmark_outline, - text: "Bookmarks", - onTap: () { - entry.remove(); - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: (context) => - Bookmarks(), - ), (route) => route.isFirst - ); - }, + (route) => route.isFirst); + }, + ), + MenuChoice( + icon: Icons.bookmark_outline, + text: "Bookmarks", + onTap: () { + entry.remove(); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => Bookmarks(), ), - - _themeProvider.getTheme==lightTheme ? - MenuChoice( - icon: Icons.mode_night, - text: "Dark", - onTap: () { - _themeProvider.setTheme(darkTheme); - setThemePref("dark", entry, context); - }, - ) : - MenuChoice( - icon: Icons.wb_sunny, - text: "Light", - onTap: () { - _themeProvider.setTheme(lightTheme); - setThemePref("light", entry, context); - }, - ), - MenuChoice( - icon: Icons.logout, - text: "Logout", - onTap: () {logOut(entry, context);}, - ), - ] - ) - ) - ] - ) - ] - ) - ) - ); + (route) => route.isFirst); + }, + ), + themeProvider.getTheme == lightTheme + ? MenuChoice( + icon: Icons.mode_night, + text: "Dark", + onTap: () { + themeProvider.setTheme(darkTheme); + setThemePref("dark", entry, context); + }, + ) + : MenuChoice( + icon: Icons.wb_sunny, + text: "Light", + onTap: () { + themeProvider.setTheme(lightTheme); + setThemePref("light", entry, context); + }, + ), + MenuChoice( + icon: Icons.logout, + text: "Logout", + onTap: () { + logOut(entry, context); + }, + ), + ])) + ]) + ]))); return entry; -} \ No newline at end of file +} diff --git a/lib/components/menu/WhiteOverlay.dart b/lib/components/menu/WhiteOverlay.dart index 5c69231..aca0f83 100644 --- a/lib/components/menu/WhiteOverlay.dart +++ b/lib/components/menu/WhiteOverlay.dart @@ -14,18 +14,19 @@ class WhiteOverlay extends CustomPainter { ..shader = const LinearGradient( begin: Alignment.bottomLeft, end: Alignment.topRight, - colors:[Colors.white24, Colors.white], + colors: [Colors.white24, Colors.white], ).createShader(Rect.fromLTRB(0, 0, size.width, size.height)); var path = Path(); path.moveTo(0, 0); path.lineTo(size.width, 0); path.lineTo(size.width, size.height); path.lineTo(0, size.height); - path.lineTo(0,0); + path.lineTo(0, 0); canvas.drawPath(path, paint); } + @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } -} \ No newline at end of file +} diff --git a/lib/components/text/GradText.dart b/lib/components/text/GradText.dart index a0fe11d..bb9a069 100644 --- a/lib/components/text/GradText.dart +++ b/lib/components/text/GradText.dart @@ -5,7 +5,12 @@ class GradText extends StatelessWidget { final Color color1; final Color color2; final double size; - const GradText({this.text, this.size, this.color1, this.color2}); + const GradText({ + required this.text, + required this.size, + required this.color1, + required this.color2 + }); @override Widget build(BuildContext context) { return ShaderMask( diff --git a/lib/components/text/TextLogo.dart b/lib/components/text/TextLogo.dart index ffce4ed..cc1a48f 100644 --- a/lib/components/text/TextLogo.dart +++ b/lib/components/text/TextLogo.dart @@ -9,11 +9,11 @@ class TextLogo extends StatelessWidget { final double width; final double height; - const TextLogo({this.color, this.width, this.height}); + const TextLogo({ required this.color, required this.width, required this.height }); @override Widget build(BuildContext context) { - var _themeProvider = Provider.of(context, listen: false); + var themeProvider = Provider.of(context, listen: false); return SizedBox( width: width, height: height, @@ -24,7 +24,7 @@ class TextLogo extends StatelessWidget { SizedBox( height: height, width: min(width*0.20, 50), - child: _themeProvider.getTheme==lightTheme ? Image.asset("lib/logos/thLogoDark.png") + child: themeProvider.getTheme==lightTheme ? Image.asset("lib/logos/thLogoDark.png") : Image.asset("lib/logos/thLogoDark.png") ), Text(" Tartanhacks ", diff --git a/lib/components/topbar/CurvedCorner.dart b/lib/components/topbar/CurvedCorner.dart index 233cd0a..8637da8 100644 --- a/lib/components/topbar/CurvedCorner.dart +++ b/lib/components/topbar/CurvedCorner.dart @@ -2,16 +2,17 @@ import 'package:flutter/material.dart'; class CurvedCorner extends CustomPainter { Color color; - CurvedCorner({this.color}); + double padding; + CurvedCorner({ required this.color, this.padding = 0}); @override void paint(Canvas canvas, Size size) { var paint = Paint() ..color = color ..strokeWidth = 15; var path = Path(); - path.moveTo(0, size.height); - path.cubicTo(.15*size.width, .3*size.height, size.width, size.height, size.width, 0); - path.lineTo(size.width, 0); + path.moveTo(0, size.height + padding * 0.5); + path.cubicTo(.15*size.width, .3*size.height + padding * 0.5, size.width, size.height + padding, size.width + padding, 0); + path.lineTo(size.width + padding, 0); path.lineTo(0, 0); canvas.drawPath(path, paint); } diff --git a/lib/components/topbar/FlagPainter.dart b/lib/components/topbar/FlagPainter.dart index 64e7faa..12b2b30 100644 --- a/lib/components/topbar/FlagPainter.dart +++ b/lib/components/topbar/FlagPainter.dart @@ -3,7 +3,7 @@ import 'dart:math'; class FlagPainter extends CustomPainter { Color color; - FlagPainter({this.color}); + FlagPainter({ required this.color }); @override void paint(Canvas canvas, Size size) { var paint = Paint() diff --git a/lib/components/topbar/TextLogo.dart b/lib/components/topbar/TextLogo.dart index 3988273..734b7cf 100644 --- a/lib/components/topbar/TextLogo.dart +++ b/lib/components/topbar/TextLogo.dart @@ -8,11 +8,11 @@ class TextLogo extends StatelessWidget { final double width; final double height; - const TextLogo({this.color, this.width, this.height}); + const TextLogo({ required this.color, required this.width, required this.height }); @override Widget build(BuildContext context) { - var _themeProvider = Provider.of(context, listen: false); + var themeProvider = Provider.of(context, listen: false); return SizedBox( width: width, height: height, @@ -23,7 +23,7 @@ class TextLogo extends StatelessWidget { SizedBox( height: height, width: min(width*0.20, 50), - child: _themeProvider.getTheme==lightTheme ? Image.asset("lib/logos/thLogoLight_small.png") + child: themeProvider.getTheme==lightTheme ? Image.asset("lib/logos/thLogoLight_small.png") : Image.asset("lib/logos/thLogoLight_small.png") ), Text(" Tartanhacks ", diff --git a/lib/components/topbar/TopBar.dart b/lib/components/topbar/TopBar.dart index 7be549a..dc62359 100644 --- a/lib/components/topbar/TopBar.dart +++ b/lib/components/topbar/TopBar.dart @@ -10,30 +10,30 @@ import './TextLogo.dart'; class TopBar extends StatelessWidget { final bool backflag; final bool isSponsor; - const TopBar({this.backflag = false, this.isSponsor = false}); + const TopBar({ this.backflag = false, this.isSponsor = false }); @override Widget build(BuildContext context) { final mqData = MediaQuery.of(context); final screenHeight = mqData.size.height; final screenWidth = mqData.size.width; + double safePadding = mqData.padding.top; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack(alignment: Alignment.topLeft, children: [ CustomPaint( size: Size(screenWidth * 0.65, screenHeight * 0.2), - painter: CurvedCorner(color: Theme.of(context).colorScheme.primary), + painter: CurvedCorner(color: Theme.of(context).colorScheme.primary, padding: safePadding), ), Container( width: screenWidth * 0.65, height: screenHeight * 0.2, alignment: Alignment.topLeft, - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: SafeArea( - child: TextLogo( - color: Theme.of(context).colorScheme.onSecondary, - width: screenWidth * 0.65, - height: screenHeight * 0.10))), + padding: EdgeInsets.fromLTRB(10, safePadding + 25, 0, 0), + child: TextLogo( + color: Theme.of(context).colorScheme.onSecondary, + width: screenWidth, + height: 50)), if (backflag) Container( width: screenWidth * 0.65, @@ -44,19 +44,19 @@ class TopBar extends StatelessWidget { Container( width: screenWidth * 0.35, alignment: Alignment.topCenter, - padding: const EdgeInsets.fromLTRB(0, 25, 0, 0), + padding: EdgeInsets.fromLTRB(0, safePadding + 20, 0, 0), child: backflag ? null : Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - SafeArea(child: HomeButton(isSponsor)), + HomeButton(isSponsor), const SizedBox(width: 10), - SafeArea(child: MenuButton(onTap: () { + MenuButton(onTap: () { if (isSponsor) { Overlay.of(context).insert(sponsorMenuOverlay(context)); } else { Overlay.of(context).insert(menuOverlay(context)); } - })), + }), const SizedBox(width: 17) ])) ], diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart deleted file mode 100644 index 6a1a4a2..0000000 --- a/lib/generated_plugin_registrant.dart +++ /dev/null @@ -1,19 +0,0 @@ -// -// Generated file. Do not edit. -// - -// ignore_for_file: directives_ordering -// ignore_for_file: lines_longer_than_80_chars -// ignore_for_file: depend_on_referenced_packages - -import 'package:shared_preferences_web/shared_preferences_web.dart'; -import 'package:url_launcher_web/url_launcher_web.dart'; - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -// ignore: public_member_api_docs -void registerPlugins(Registrar registrar) { - SharedPreferencesPlugin.registerWith(registrar); - UrlLauncherPlugin.registerWith(registrar); - registrar.registerMessageHandler(); -} diff --git a/lib/logos/thLogoDark.png b/lib/logos/thLogoDark.png index 1455987..0bf9069 100644 Binary files a/lib/logos/thLogoDark.png and b/lib/logos/thLogoDark.png differ diff --git a/lib/logos/thLogoDark_small.png b/lib/logos/thLogoDark_small.png index ff70517..7d19f1d 100644 Binary files a/lib/logos/thLogoDark_small.png and b/lib/logos/thLogoDark_small.png differ diff --git a/lib/logos/thLogoLight.png b/lib/logos/thLogoLight.png index 0cf3f8f..5c11921 100644 Binary files a/lib/logos/thLogoLight.png and b/lib/logos/thLogoLight.png differ diff --git a/lib/logos/thLogoLight_small.png b/lib/logos/thLogoLight_small.png index 66eea02..a92d5dd 100644 Binary files a/lib/logos/thLogoLight_small.png and b/lib/logos/thLogoLight_small.png differ diff --git a/lib/main.dart b/lib/main.dart index b32dfbf..eae543d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,13 +6,15 @@ import 'package:thdapp/providers/check_in_items_provider.dart'; import 'package:thdapp/providers/user_info_provider.dart'; import 'pages/login.dart'; import 'theme_changer.dart'; +import 'package:thdapp/providers/expo_config_provider.dart'; void main() => runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => CheckInItemsModel()), ChangeNotifierProvider(create: (_) => ThemeChanger(lightTheme)), - ChangeNotifierProvider(create: (context) => UserInfoModel()) + ChangeNotifierProvider(create: (context) => UserInfoModel()), + ChangeNotifierProvider(create: (_) => ExpoConfigProvider()), ], child: MyApp() ) diff --git a/lib/models/check_in_item.dart b/lib/models/check_in_item.dart index f5742a5..dc8993b 100644 --- a/lib/models/check_in_item.dart +++ b/lib/models/check_in_item.dart @@ -10,17 +10,18 @@ class CheckInItem { final int endTime; final String event; //objectid - CheckInItem( - {this.points, - this.accessLevel, - this.active, - this.enableSelfCheckIn, - this.id, - this.name, - this.description, - this.startTime, - this.endTime, - this.event}); + CheckInItem({ + required this.points, + required this.accessLevel, + required this.active, + required this.enableSelfCheckIn, + required this.id, + required this.name, + required this.description, + required this.startTime, + required this.endTime, + required this.event + }); factory CheckInItem.fromJson(Map parsedJson) { return CheckInItem( @@ -47,14 +48,15 @@ class CheckInItemDTO { final String accessLevel; final bool enableSelfCheckIn; - CheckInItemDTO( - {this.points, - this.accessLevel, - this.name, - this.description, - this.startTime, - this.endTime, - this.enableSelfCheckIn}); + CheckInItemDTO({ + required this.points, + required this.accessLevel, + required this.name, + required this.description, + required this.startTime, + required this.endTime, + required this.enableSelfCheckIn + }); CheckInItemDTO.fromCheckInItem(CheckInItem item): name = item.name, diff --git a/lib/models/config.dart b/lib/models/config.dart new file mode 100644 index 0000000..6858d4d --- /dev/null +++ b/lib/models/config.dart @@ -0,0 +1,16 @@ +class ExpoConfig { + final DateTime expoStartTime; + final DateTime submissionDeadline; + + ExpoConfig({ + required this.expoStartTime, + required this.submissionDeadline, + }); + + factory ExpoConfig.fromJson(Map json) { + return ExpoConfig( + expoStartTime: DateTime.parse(json['expoStartTime']), + submissionDeadline: DateTime.parse(json['submissionDeadline']), + ); + } +} \ No newline at end of file diff --git a/lib/models/discord.dart b/lib/models/discord.dart index d2dfe9c..3ef9772 100644 --- a/lib/models/discord.dart +++ b/lib/models/discord.dart @@ -5,9 +5,10 @@ class DiscordInfo { DiscordInfo({ - this.code, - this.expiry, - this.link}); + required this.code, + required this.expiry, + required this.link + }); factory DiscordInfo.fromJson(Map parsedJson) { diff --git a/lib/models/event.dart b/lib/models/event.dart index 4871d18..6f7f534 100644 --- a/lib/models/event.dart +++ b/lib/models/event.dart @@ -3,24 +3,24 @@ class Event { final String platform; final bool active; final String name; - final String description; + final String? description; //time is unix final int startTime; final int endTime; final String location; - final int lat; - final int lng; - final String platformUrl; + final int? lat; + final int? lng; + final String? platformUrl; Event({ - this.id, - this.platform, - this.active, - this.name, + required this.id, + required this.platform, + required this.active, + required this.name, this.description, - this.startTime, - this.endTime, - this.location, + required this.startTime, + required this.endTime, + required this.location, this.lat, this.lng, this.platformUrl @@ -46,21 +46,22 @@ class Event { class EventDTO { final String platform; final String name; - final String description; + final String? description; //time is unix final int startTime; final int endTime; final String location; - final String platformUrl; + final String? platformUrl; - EventDTO( - {this.name, - this.description, - this.startTime, - this.endTime, - this.platform, - this.platformUrl, - this.location}); + EventDTO({ + required this.name, + required this.description, + required this.startTime, + required this.endTime, + required this.platform, + required this.platformUrl, + required this.location + }); EventDTO.fromEventItem(Event item): name = item.name, diff --git a/lib/models/lb_entry.dart b/lib/models/lb_entry.dart index 4aa23e7..b4ba209 100644 --- a/lib/models/lb_entry.dart +++ b/lib/models/lb_entry.dart @@ -1,16 +1,18 @@ class LBEntry { - final String user; final int totalPoints; final String displayName; final int rank; - LBEntry({this.user, this.totalPoints, this.displayName, this.rank}); + LBEntry({ + required this.totalPoints, + required this.displayName, + required this.rank + }); factory LBEntry.fromJson(Map parsedJson) { return LBEntry( - user: parsedJson['user'], totalPoints: parsedJson['totalPoints'] ?? 0, - displayName: parsedJson['displayName'], + displayName: parsedJson['displayName'] ?? 'Unknown User', rank: parsedJson['rank'] ); } diff --git a/lib/models/member.dart b/lib/models/member.dart index 9ba706c..c0764ee 100644 --- a/lib/models/member.dart +++ b/lib/models/member.dart @@ -5,22 +5,22 @@ class Member { final String email; Member({ - this.id, - this.isAdmin, - this.name, - this.email, + required this.id, + required this.isAdmin, + required this.name, + required this.email, }); factory Member.fromJson(Map parsedJson, String adminID) { // var parsedJson = jsonDecode(parsedString); bool isAdminBool = false; String currID = parsedJson["_id"]; - if(currID == adminID) isAdminBool = true; + if (currID == adminID) isAdminBool = true; return Member( - id: parsedJson["_id"], + id: parsedJson["_id"], isAdmin: isAdminBool, - name: (parsedJson["firstName"]??"") + " " + (parsedJson["lastName"]??""), - email: parsedJson["email"] - ); + name: + "${parsedJson["firstName"] ?? ""} ${parsedJson["lastName"] ?? ""}", + email: parsedJson["email"]); } } diff --git a/lib/models/old/event_model.dart b/lib/models/old/event_model.dart index e254d59..3d54234 100644 --- a/lib/models/old/event_model.dart +++ b/lib/models/old/event_model.dart @@ -20,21 +20,22 @@ class Event { final int duration; - Event({this.zoom_access_enabled, - this.is_in_person, - this.id, - this.name, - this.description, - this.timestamp, - this.gcal_event_url, - this.zoom_link, - this.access_code, - this.zoom_id, - this.zoom_password, - this.created_at, - this.v, - this.duration} - ); + Event({ + required this.zoom_access_enabled, + required this.is_in_person, + required this.id, + required this.name, + required this.description, + required this.timestamp, + required this.gcal_event_url, + required this.zoom_link, + required this.access_code, + required this.zoom_id, + required this.zoom_password, + required this.created_at, + required this.v, + required this.duration + }); factory Event.fromJson(Map parsedJson) { return new Event( diff --git a/lib/models/old/login_model.dart b/lib/models/old/login_model.dart index d297f4f..f8a59ab 100644 --- a/lib/models/old/login_model.dart +++ b/lib/models/old/login_model.dart @@ -7,7 +7,10 @@ class Login { final Participant user; final String access_token; - Login({this.user, this.access_token}); + Login({ + required this.user, + required this.access_token + }); factory Login.fromJson(Map parsedJson) { return new Login( diff --git a/lib/models/old/participant_model.dart b/lib/models/old/participant_model.dart index 86c3b37..7ae29c5 100644 --- a/lib/models/old/participant_model.dart +++ b/lib/models/old/participant_model.dart @@ -19,22 +19,23 @@ class Participant { final int v; final String last_login_time; - Participant( - {this.is_on_mobile, - this.is_admin, - this.total_points, - this.id, - this.name, - this.email, - this.reg_system_id, - this.team_id, - this.dp_url, - this.is_active, - this.github_profile_url, - this.resume_url, - this.account_creation_time, - this.v, - this.last_login_time}); + Participant({ + required this.is_on_mobile, + required this.is_admin, + required this.total_points, + required this.id, + required this.name, + required this.email, + required this.reg_system_id, + required this.team_id, + required this.dp_url, + required this.is_active, + required this.github_profile_url, + required this.resume_url, + required this.account_creation_time, + required this.v, + required this.last_login_time + }); factory Participant.fromJson(Map parsedJson) { return new Participant( diff --git a/lib/models/old/prize.dart b/lib/models/old/prize.dart index 1ca306b..e4f6242 100644 --- a/lib/models/old/prize.dart +++ b/lib/models/old/prize.dart @@ -3,7 +3,11 @@ class Prize{ final String name; final String description; - Prize({this.id, this.name, this.description}); + Prize({ + required this.id, + required this.name, + required this.description + }); factory Prize.fromJson(Map parsedJson) { Prize newPrize = new Prize( diff --git a/lib/models/old/project.dart b/lib/models/old/project.dart index 00e397b..ec88d53 100644 --- a/lib/models/old/project.dart +++ b/lib/models/old/project.dart @@ -11,7 +11,16 @@ class Project{ final List prizes; final String id; - Project({this.name, this.desc, this.slides, this.video, this.willPresent, this.prizes, this.github, this.id}); + Project({ + required this.name, + required this.desc, + required this.slides, + required this.video, + required this.willPresent, + required this.prizes, + required this.github, + required this.id + }); factory Project.fromJson(Map parsedJson) { var jsonList = parsedJson['eligible_prizes'] as List; diff --git a/lib/models/participant_bookmark.dart b/lib/models/participant_bookmark.dart index b2a5684..a9440a5 100644 --- a/lib/models/participant_bookmark.dart +++ b/lib/models/participant_bookmark.dart @@ -7,12 +7,12 @@ class ParticipantBookmark { final ParticipantInfo participantData; ParticipantBookmark({ - this.bookmarkId, - this.bookmarkType, + required this.bookmarkId, + required this.bookmarkType, - this.description, - this.createdAt, - this.participantData, // json data format + required this.description, + required this.createdAt, + required this.participantData, // stringified JSON // participantId, participantEmail, participantFirstName, participantLastName, participantResume }); @@ -38,7 +38,12 @@ class ParticipantInfo { final String firstName; final String lastName; - ParticipantInfo({this.id, this.email, this.firstName, this.lastName}); + ParticipantInfo({ + required this.id, + required this.email, + required this.firstName, + required this.lastName + }); factory ParticipantInfo.fromJson(Map parsedJson) { return ParticipantInfo( diff --git a/lib/models/prize.dart b/lib/models/prize.dart index 5e9486c..bf2bb59 100644 --- a/lib/models/prize.dart +++ b/lib/models/prize.dart @@ -3,19 +3,20 @@ class Prize{ final String event; //objectid final String name; final String description; - final String eligibility; - final String provider; //objectid - final String winner; //objectid + final String? eligibility; + final String? provider; //objectid + final String? winner; //objectid - Prize( - {this.id, - this.event, - this.name, - this.description, - this.eligibility, - this.provider, - this.winner}); + Prize({ + required this.id, + required this.event, + required this.name, + required this.description, + this.eligibility, + this.provider, + this.winner + }); factory Prize.fromJson(Map parsedJson) { Prize newPrize = Prize( diff --git a/lib/models/profile.dart b/lib/models/profile.dart index ba16e61..e22ed16 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -2,90 +2,87 @@ class Profile { final int totalPoints; final String user; final String event; //objectid - final String email; final String firstName; final String lastName; - final int age; + final int? age; final String school; - final String college; - final String level; + final String? college; + final String? level; final int graduationYear; final String gender; - final String genderOther; + final String? genderOther; final String ethnicity; - final String ethnicityOther; + final String? ethnicityOther; final String phoneNumber; - final String major; - final String coursework; - final String language; - final String hackathonExperience; - final String workPermission; - final String workLocation; - final String workStrengths; - final List sponsorRanking; //objectid + final String? major; + final String? coursework; + final String? language; + final String? hackathonExperience; + final String? workPermission; + final String? workLocation; + final String? workStrengths; + final List? sponsorRanking; //objectid final String github; - final String resume; - final String design; - final String website; - final List essays; - final List dietaryRestrictions; - final String shirtSize; - final bool wantsHardware; - final String address; - final String region; + final String? resume; + final String? design; + final String? website; + final List? essays; + final List? dietaryRestrictions; + final String? shirtSize; + final bool? wantsHardware; + final String? address; + final String? region; final String displayName; - String profilePicture; + final String? profilePicture; - Profile({ - this.totalPoints, - this.user, - this.event, - this.email, - this.firstName, - this.lastName, - this.age, - this.school, - this.college, - this.level, - this.graduationYear, - this.gender, - this.genderOther, - this.ethnicity, - this.ethnicityOther, - this.phoneNumber, - this.major, - this.coursework, - this.language, - this.hackathonExperience, - this.workPermission, - this.workLocation, - this.workStrengths, - this.sponsorRanking, - this.github, - this.resume, - this.design, - this.website, - this.essays, - this.dietaryRestrictions, - this.shirtSize, - this.wantsHardware, - this.address, - this.region, - this.displayName, - this.profilePicture}); + Profile( + {required this.totalPoints, + required this.user, + required this.event, + required this.firstName, + required this.lastName, + this.age, + required this.school, + this.college, + this.level, + required this.graduationYear, + required this.gender, + this.genderOther, + required this.ethnicity, + this.ethnicityOther, + required this.phoneNumber, + this.major, + this.coursework, + this.language, + this.hackathonExperience, + this.workPermission, + this.workLocation, + this.workStrengths, + this.sponsorRanking, + required this.github, + this.resume, + this.design, + this.website, + this.essays, + this.dietaryRestrictions, + this.shirtSize, + this.wantsHardware, + this.address, + this.region, + required this.displayName, + this.profilePicture}); factory Profile.fromJson(Map parsedJson) { return Profile( totalPoints: parsedJson['totalPoints'], user: parsedJson['user'], event: parsedJson['event'], - email: parsedJson['email'], firstName: parsedJson['firstName'], lastName: parsedJson['lastName'], age: parsedJson['age'], school: parsedJson['school'], college: parsedJson['college'], - level: parsedJson['level'], + level: parsedJson['collegeLevel'], graduationYear: parsedJson['graduationYear'], gender: parsedJson['gender'], genderOther: parsedJson['genderOther'], @@ -111,7 +108,6 @@ class Profile { address: parsedJson['address'], region: parsedJson['region'], displayName: parsedJson['displayName'], - profilePicture: parsedJson['profilePicture'] - ); + profilePicture: parsedJson['profilePicture']); } } \ No newline at end of file diff --git a/lib/models/project.dart b/lib/models/project.dart index 33c5a27..7388253 100644 --- a/lib/models/project.dart +++ b/lib/models/project.dart @@ -9,18 +9,21 @@ class Project{ final String team; //objectid final List prizes; //objectid final bool presentingVirtually; + final int? tableNumber; - Project( - {this.id, - this.name, - this.desc, - this.event, - this.url, - this.slides, - this.video, - this.team, - this.prizes, - this.presentingVirtually}); + Project({ + required this.id, + required this.name, + required this.desc, + required this.event, + required this.url, + required this.slides, + required this.video, + required this.team, + required this.prizes, + required this.presentingVirtually, + this.tableNumber, + }); factory Project.fromJson(Map parsedJson) { Project project = Project( @@ -33,7 +36,8 @@ class Project{ video: parsedJson['video'], team: parsedJson['team'], prizes: parsedJson['prizes'], - presentingVirtually: parsedJson['presentingVirtually'] + presentingVirtually: parsedJson['presentingVirtually'], + tableNumber: parsedJson['tableNumber'], ); return project; } diff --git a/lib/models/project_bookmark.dart b/lib/models/project_bookmark.dart index d3fa641..5b8007d 100644 --- a/lib/models/project_bookmark.dart +++ b/lib/models/project_bookmark.dart @@ -8,13 +8,11 @@ class ProjectBookmark { final String createdAt; ProjectBookmark({ - this.bookmarkId, - this.bookmarkType, - - this.projectData, // json data format - - this.description, - this.createdAt, + required this.bookmarkId, + required this.bookmarkType, + required this.projectData, // stringified JSON + required this.description, + required this.createdAt, }); factory ProjectBookmark.fromJson(Map parsedJson) { diff --git a/lib/models/team.dart b/lib/models/team.dart index d54f617..d61037f 100644 --- a/lib/models/team.dart +++ b/lib/models/team.dart @@ -10,12 +10,13 @@ class Team { Team({ - this.teamID, - this.visible, - this.admins, - this.name, - this.members, - this.description}); + required this.teamID, + required this.visible, + required this.admins, + required this.name, + required this.members, + required this.description + }); factory Team.fromJson(Map parsedJson) { // var parsedJson = jsonDecode(parseString); diff --git a/lib/models/user.dart b/lib/models/user.dart index 80d1c20..f3b2135 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -4,11 +4,18 @@ class User { final String id; final String email; final String token; - final String company; + String? company; final String status; - User({this.admin, this.judge, this.id, this.email, this.token, this.company, - this.status}); + User({ + required this.admin, + required this.judge, + required this.id, + required this.email, + required this.token, + this.company, + required this.status + }); factory User.fromJson(Map parsedJson) { return User( diff --git a/lib/pages/admin/manage_tables.dart b/lib/pages/admin/manage_tables.dart new file mode 100644 index 0000000..ca9c9e4 --- /dev/null +++ b/lib/pages/admin/manage_tables.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:thdapp/components/DefaultPage.dart'; +import 'package:thdapp/components/buttons/GradBox.dart'; +import 'package:thdapp/components/ErrorDialog.dart'; +import 'package:thdapp/api.dart'; +import 'package:thdapp/models/project.dart'; +import 'package:thdapp/models/config.dart'; +import 'package:provider/provider.dart'; +import 'package:thdapp/providers/user_info_provider.dart'; + +class ManageTablesPage extends StatefulWidget { + @override + _ManageTablesPageState createState() => _ManageTablesPageState(); +} + +class _ManageTablesPageState extends State { + List projects = []; + bool isLoading = true; + ExpoConfig? expoConfig; + + @override + void initState() { + super.initState(); + _loadData(); + } + + Future _loadData() async { + setState(() => isLoading = true); + try { + final token = Provider.of(context, listen: false).token; + projects = await getAllProjects(token); + expoConfig = await getExpoConfig(token); + } catch (e) { + errorDialog(context, "Error", "Failed to load data"); + } finally { + setState(() => isLoading = false); + } + } + + Widget _buildExpoStatus() { + if (expoConfig == null) return const SizedBox.shrink(); + + final now = DateTime.now(); + final isBeforeExpo = now.isBefore(expoConfig!.expoStartTime); + + return Container( + padding: const EdgeInsets.all(8), + color: isBeforeExpo + ? Colors.green.withAlpha(50) + : Colors.orange.withAlpha(50), + child: Text( + isBeforeExpo + ? "Participants can still submit table numbers" + : "Expo has started - Only admin can modify tables", + style: Theme.of(context).textTheme.bodyMedium, + ), + ); + } + + Future _updateTableNumber( + Project project, String newTableNumber) async { + try { + int? tableNum = int.tryParse(newTableNumber); + if (tableNum != null) { + // TODO: Add API call to update table number + // await updateProjectTableNumber(project.id, tableNum); + await _loadData(); // Refresh the list + } + } catch (e) { + errorDialog(context, "Error", "Failed to update table number"); + } + } + + @override + Widget build(BuildContext context) { + return DefaultPage( + backflag: true, + child: Container( + padding: const EdgeInsets.all(16), + child: GradBox( + child: Column( + children: [ + Text( + "Manage Project Tables", + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 16), + _buildExpoStatus(), + const SizedBox(height: 16), + if (isLoading) + const CircularProgressIndicator() + else + Expanded( + child: ListView.builder( + itemCount: projects.length, + itemBuilder: (context, index) { + final project = projects[index]; + return ListTile( + title: Text(project.name), + subtitle: Text( + "Current Table: ${project.tableNumber ?? 'Not assigned'}"), + trailing: SizedBox( + width: 100, + child: TextField( + keyboardType: TextInputType.number, + decoration: const InputDecoration( + labelText: "Table #", + ), + onSubmitted: (value) => + _updateTableNumber(project, value), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/bookmarks.dart b/lib/pages/bookmarks.dart index b0220be..a97df1c 100644 --- a/lib/pages/bookmarks.dart +++ b/lib/pages/bookmarks.dart @@ -7,29 +7,25 @@ import 'package:thdapp/api.dart'; import 'profile_page.dart'; import '../models/participant_bookmark.dart'; - class Bookmarks extends StatefulWidget { @override _Bookmarks createState() => _Bookmarks(); } class _Bookmarks extends State { - List participantBookmarks; - List projectBookmarks; - Map bookmarksMap; - SharedPreferences prefs; - String token; - + late List participantBookmarks; + late List projectBookmarks; + late Map bookmarksMap; + late SharedPreferences prefs; + late String token; void getData() async { prefs = await SharedPreferences.getInstance(); - token = prefs.getString('token'); + token = prefs.getString('token')!; participantBookmarks = await getParticipantBookmarks(token); projectBookmarks = await getProjectBookmarks(token); bookmarksMap = await getBookmarkIdsList(token); - setState(() { - - }); + setState(() {}); } void removeBookmark(String id) { @@ -39,14 +35,15 @@ class _Bookmarks extends State { // return object of type Dialog return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Confirm", style: Theme.of(context).textTheme.headline1), - content: Text("Are you sure you want to delete this bookmark?", style: Theme.of(context).textTheme.bodyText2), + title: Text("Confirm", style: Theme.of(context).textTheme.displayLarge), + content: Text("Are you sure you want to delete this bookmark?", + style: Theme.of(context).textTheme.bodyMedium), actions: [ // usually buttons at the bottom of the dialog TextButton( child: Text( "Cancel", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -55,7 +52,7 @@ class _Bookmarks extends State { TextButton( child: Text( "Yes", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { deleteBookmark(token, id); @@ -65,9 +62,7 @@ class _Bookmarks extends State { break; } } - setState(() { - - }); + setState(() {}); Navigator.of(context).pop(); }, ), @@ -90,78 +85,70 @@ class _Bookmarks extends State { final screenWidth = mqData.size.width; return DefaultPage( - isSponsor: true, - reverse: true, - child: - Column( - children: [ - SizedBox( - height: screenHeight * 0.80, - child: Column( - children: [ - Container( - alignment: Alignment.topLeft, - padding: EdgeInsets.fromLTRB(screenWidth * 0.08, 0, screenWidth * 0.08, 0), - child: Text("BOOKMARKS", - style: Theme.of(context).textTheme.headline2), - ), - Container( - alignment: Alignment.topLeft, - padding: EdgeInsets.fromLTRB(screenWidth * 0.08, 0, screenWidth * 0.08, 0), - child: Text( - "Scroll to see the full list.", - style: TextStyle( - fontSize: screenHeight * 0.02)), - ), - const SizedBox(height: 5), - if(participantBookmarks!=null) - Expanded( - child: (participantBookmarks.isNotEmpty) ? - Container( - padding: EdgeInsets.fromLTRB(screenWidth * 0.05, 0, screenWidth * 0.05, 0), - alignment: Alignment.topCenter, - child: ListView.builder( - itemCount: participantBookmarks.length, - itemBuilder: (BuildContext context, int index) { - return BookmarkInfo( - bmID: participantBookmarks[index].bookmarkId, - data: participantBookmarks[index].participantData, - team: "ScottyLabs", - bio: "[Bio]", - remove: removeBookmark, - bmMap: bookmarksMap, - refresh: getData, - ); - }, - ) - ) : - Container( - padding: EdgeInsets.fromLTRB(screenWidth * 0.05, 0, screenWidth * 0.05, 0), - alignment: Alignment.center, - child: Text( - "No bookmarks.", - style: Theme.of(context).textTheme.headline3 - .copyWith(color: Theme.of(context).colorScheme.onPrimary) - ) - ) - ) - else - Container( + isSponsor: true, + reverse: true, + child: Column( + children: [ + SizedBox( + height: screenHeight * 0.80, + child: Column(children: [ + Container( + alignment: Alignment.topLeft, + padding: EdgeInsets.fromLTRB( + screenWidth * 0.08, 0, screenWidth * 0.08, 0), + child: Text("BOOKMARKS", + style: Theme.of(context).textTheme.displayMedium), + ), + Container( + alignment: Alignment.topLeft, + padding: EdgeInsets.fromLTRB( + screenWidth * 0.08, 0, screenWidth * 0.08, 0), + child: Text("Scroll to see the full list.", + style: TextStyle(fontSize: screenHeight * 0.02)), + ), + const SizedBox(height: 5), + Expanded( + child: (participantBookmarks.isNotEmpty) + ? Container( + padding: EdgeInsets.fromLTRB(screenWidth * 0.05, + 0, screenWidth * 0.05, 0), + alignment: Alignment.topCenter, + child: ListView.builder( + itemCount: participantBookmarks.length, + itemBuilder: + (BuildContext context, int index) { + return BookmarkInfo( + bmID: participantBookmarks[index] + .bookmarkId, + data: participantBookmarks[index] + .participantData, + team: "ScottyLabs", + bio: "[Bio]", + remove: removeBookmark, + bmMap: bookmarksMap, + refresh: getData, + ); + }, + )) + : Container( + padding: EdgeInsets.fromLTRB(screenWidth * 0.05, + 0, screenWidth * 0.05, 0), alignment: Alignment.center, - padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), - child: const CircularProgressIndicator() - ) - ] - ) - ) - ], // children - ) - ); + child: Text("No bookmarks.", + style: Theme.of(context) + .textTheme + .displaySmall + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onPrimary)))) + ])) + ], // children + )); } } class BookmarkInfo extends StatelessWidget { - final ParticipantInfo data; final String bmID; final String team; @@ -170,7 +157,14 @@ class BookmarkInfo extends StatelessWidget { final Map bmMap; final Function refresh; - const BookmarkInfo({this.bmID, this.data, this.team, this.bio, this.remove, this.bmMap, this.refresh}); + const BookmarkInfo( + {required this.bmID, + required this.data, + required this.team, + required this.bio, + required this.remove, + required this.bmMap, + required this.refresh}); @override Widget build(BuildContext context) { @@ -180,59 +174,53 @@ class BookmarkInfo extends StatelessWidget { alignment: Alignment.topLeft, width: 200, height: 180, - child: Row( - children: [ - Container( - width: 230, - padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - - data.firstName + " " + data.lastName, - style: Theme.of(context).textTheme.headline4 - ), - Text( - team, - style: Theme.of(context).textTheme.bodyText2 - ), - /*Text( + child: Row(children: [ + Container( + width: 230, + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("${data.firstName} ${data.lastName}", + style: Theme.of(context).textTheme.headlineMedium), + Text(team, style: Theme.of(context).textTheme.bodyMedium), + /*Text( bio, - style: Theme.of(context).textTheme.bodyText2 + style: Theme.of(context).textTheme.bodyMedium ),*/ - const SizedBox(height: 18), - SolidButton( - text: "View More", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => - ProfilePage(bookmarks: bmMap,), - settings: RouteSettings( - arguments: data.id, - )), - ).then((value) => refresh()); - }, - ) - ] - ), - ), - RawMaterialButton( - onPressed: () {remove(bmID);}, - elevation: 2.0, - fillColor: Theme.of(context).colorScheme.primary, - child: Icon( - Icons.bookmark, - size: 50.0, - color: Theme.of(context).colorScheme.onPrimary, - ), - padding: const EdgeInsets.all(12), - shape: const CircleBorder(), - ), - ] - ) - ), + const SizedBox(height: 18), + SolidButton( + text: "View More", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ProfilePage( + bookmarks: bmMap, + ), + settings: RouteSettings( + arguments: data.id, + )), + ).then((value) => refresh()); + }, + ) + ]), + ), + RawMaterialButton( + onPressed: () { + remove(bmID); + }, + elevation: 2.0, + fillColor: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.all(12), + shape: const CircleBorder(), + child: Icon( + Icons.bookmark, + size: 50.0, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ])), ); } -} \ No newline at end of file +} diff --git a/lib/pages/checkin.dart b/lib/pages/checkin.dart index 88e6669..83211a2 100644 --- a/lib/pages/checkin.dart +++ b/lib/pages/checkin.dart @@ -32,47 +32,39 @@ class _CheckInState extends State { final screenWidth = mqData.size.width; return DefaultPage( - reverse: true, - child: - Consumer( - builder: (context, checkInItemsModel, child) { - var status = - checkInItemsModel.checkInItemsStatus; - var checkInItemsList = - checkInItemsModel.checkInItems; - if (status == Status.notLoaded || - checkInItemsList == null) { - checkInItemsModel.fetchCheckInItems(); - return const Center( - child: CircularProgressIndicator()); - } - // Error - else if (status == Status.error) { - return const Center( - child: Text("Error Loading Data")); - } else { - return Container( - alignment: Alignment.center, - height: screenHeight * 0.78, - child: Column( - children: [ - Expanded(flex: 1, child: Header()), - Expanded( - flex: 2, child: CheckInEvents()) - ], - )); - } - }, - ) - ); + reverse: true, + child: Consumer( + builder: (context, checkInItemsModel, child) { + var status = checkInItemsModel.checkInItemsStatus; + var checkInItemsList = checkInItemsModel.checkInItems; + if (status == Status.notLoaded) { + checkInItemsModel.fetchCheckInItems(); + return const Center(child: CircularProgressIndicator()); + } + // Error + else if (status == Status.error) { + return const Center(child: Text("Error Loading Data")); + } else { + return Container( + alignment: Alignment.center, + height: screenHeight * 0.78, + child: Column( + children: [ + Expanded(flex: 1, child: Header()), + Expanded(flex: 2, child: CheckInEvents()) + ], + )); + } + }, + )); } } class Header extends StatelessWidget { @override Widget build(BuildContext context) { - var isAdmin = Provider.of(context).isAdmin; - var points = Provider.of(context).points; + bool isAdmin = Provider.of(context).isAdmin ?? false; + int points = Provider.of(context).points ?? -1; return Container( padding: const EdgeInsets.fromLTRB(30, 0, 15, 0), child: Row( @@ -105,7 +97,7 @@ class QRHeader extends StatelessWidget { height: 30, child: Text( "Get Checked In", - style: Theme.of(context).textTheme.bodyText2?.copyWith( + style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.bold, decoration: TextDecoration.underline, fontSize: 16), @@ -120,12 +112,19 @@ class QRHeader extends StatelessWidget { ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.primary, borderRadius: const BorderRadius.all(Radius.circular(16))), - child: QrImage( + child: QrImageView( data: id, version: QrVersions.auto, - foregroundColor: isLight - ? Theme.of(context).accentColor - : Theme.of(context).colorScheme.onPrimary, + eyeStyle: QrEyeStyle( + color: isLight + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.onPrimary, + ), + dataModuleStyle: QrDataModuleStyle( + color: isLight + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.onPrimary, + ), )), ) ], @@ -149,18 +148,18 @@ class PointsHeader extends StatelessWidget { ), Text( "CHECKIN", - style: Theme.of(context).textTheme.headline1, + style: Theme.of(context).textTheme.displayLarge, ), const SizedBox( height: 2, ), Text( "Points earned:", - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ), Text( "$points pts", - style: Theme.of(context).textTheme.headline2, + style: Theme.of(context).textTheme.displayMedium, ) ], ); @@ -176,14 +175,14 @@ class AdminHeader extends StatelessWidget { children: [ Text( "CHECKIN", - style: Theme.of(context).textTheme.headline1, + style: Theme.of(context).textTheme.displayLarge, ), const SizedBox( height: 2, ), Text( "Admin Dashboard", - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ) ], ); @@ -194,8 +193,9 @@ class CheckInEvents extends StatelessWidget { @override Widget build(BuildContext context) { var model = Provider.of(context); - var editable = model.isAdmin; - var checkInItemsList = model.checkInItems; + var editable = model.isAdmin ?? false; + List checkInItemsList = + model.checkInItems as List; return RefreshIndicator( onRefresh: model.fetchCheckInItems, child: Padding( @@ -211,21 +211,22 @@ class CheckInEvents extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 10), child: GradBox( + onTap: () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const EditCheckInItemPage(null))) + }, + curvature: 12, child: Text( "NEW CHECKIN ITEM", textAlign: TextAlign.center, style: Theme.of(context) .textTheme - .bodyText2 + .bodyMedium ?.copyWith(fontSize: 30, fontWeight: FontWeight.bold), ), - onTap: () => { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const EditCheckInItemPage(null))) - }, - curvature: 12, ), ) ], @@ -243,7 +244,7 @@ class CheckInEventList extends StatelessWidget { @override Widget build(BuildContext context) { var model = Provider.of(context); - var editable = model.isAdmin; + bool editable = model.isAdmin ?? false; String userID = model.userID; bool isAdmin = editable; var hasCheckedIn = model.hasCheckedIn; @@ -254,7 +255,8 @@ class CheckInEventList extends StatelessWidget { return CheckInEventListItem( name: events[index].name, points: events[index].points, - isChecked: editable ? false : hasCheckedIn[events[index].id], + isChecked: + editable ? false : (hasCheckedIn?[events[index].id] ?? false), enabled: events[index].enableSelfCheckIn, onTap: () { Navigator.push( @@ -267,15 +269,14 @@ class CheckInEventList extends StatelessWidget { String checkInItemId = ""; if (isAdmin) { checkInItemId = events[index].id; - uid = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR); + uid = await FlutterBarcodeScanner.scanBarcode( + '#ff6666', 'Cancel', true, ScanMode.QR); } else { uid = userID; - checkInItemId = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR); + checkInItemId = await FlutterBarcodeScanner.scanBarcode( + '#ff6666', 'Cancel', true, ScanMode.QR); } - if (uid != null && - uid != "" && - checkInItemId != null && - checkInItemId != "") { + if (uid != "" && checkInItemId != "") { showDialog( context: context, barrierDismissible: false, @@ -287,12 +288,13 @@ class CheckInEventList extends StatelessWidget { if (uid != "-1" && checkInItemId != "-1") { await model.checkInUser(checkInItemId, uid); ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text("Checked in for " + events[index].name + "!"), + content: Text("Checked in for ${events[index].name}!"), )); } - } on Exception catch (e) { + } on Exception { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Could not check in. Please ensure that the QR code is correct."), + content: Text( + "Could not check in. Please ensure that the QR code is correct."), )); } finally { Navigator.pop(context); @@ -319,20 +321,20 @@ class CheckInEventListItem extends StatelessWidget { final bool enabled; final Function onCheck; - final Function onTap; + final void Function()? onTap; final int points; const CheckInEventListItem( - {this.name, - this.isChecked, - this.enabled, - this.onTap, - this.onCheck, - this.points}); + {required this.name, + required this.isChecked, + required this.enabled, + required this.onTap, + required this.onCheck, + required this.points}); @override Widget build(BuildContext context) { - var editable = Provider.of(context).isAdmin; + bool editable = Provider.of(context).isAdmin ?? false; bool isAdmin = editable; return IntrinsicHeight( child: Row( @@ -354,7 +356,7 @@ class CheckInEventListItem extends StatelessWidget { softWrap: false, style: Theme.of(context) .textTheme - .headline1 + .displayLarge ?.copyWith(fontSize: 23), ), InkWell( @@ -402,7 +404,7 @@ class CheckInEventListItem extends StatelessWidget { overflow: TextOverflow.fade, maxLines: 1, softWrap: false, - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ), ) ], @@ -421,6 +423,7 @@ class CheckInEventListItem extends StatelessWidget { Expanded( flex: 20, child: SolidButton( + onPressed: onTap, child: FittedBox( child: Text( editable ? "Edit\nItem" : "View\nItem", @@ -433,7 +436,6 @@ class CheckInEventListItem extends StatelessWidget { maxLines: 2, ), ), - onPressed: onTap, ), ) ], diff --git a/lib/pages/checkin_qr.dart b/lib/pages/checkin_qr.dart index a861a89..e1d4f8f 100644 --- a/lib/pages/checkin_qr.dart +++ b/lib/pages/checkin_qr.dart @@ -2,14 +2,15 @@ import 'package:flutter_smart_scan/flutter_smart_scan.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:thdapp/api.dart'; import 'package:thdapp/components/DefaultPage.dart'; import 'package:thdapp/components/buttons/SolidButton.dart'; +import 'package:thdapp/models/profile.dart'; import 'package:thdapp/providers/check_in_items_provider.dart'; import '../theme_changer.dart'; - class QRPage extends StatefulWidget { - @override State createState() => _QRPageState(); } @@ -17,40 +18,74 @@ class QRPage extends StatefulWidget { class _QRPageState extends State { final _eventIDController = TextEditingController(); + late Profile userData; + late String id; + late String token; + + void getData() async { + prefs = await SharedPreferences.getInstance(); + + token = prefs.getString('token')!; + id = prefs.getString('id')!; + + userData = await getProfile(id, token); + + setState(() {}); + } + + @override + initState() { + super.initState(); + getData(); + } + @override Widget build(BuildContext context) { final mqData = MediaQuery.of(context); final screenHeight = mqData.size.height; final screenWidth = mqData.size.width; + String dietaryRestrictions; + if (userData.dietaryRestrictions!.isEmpty) { + dietaryRestrictions = "No dietary restrictions"; + } else { + dietaryRestrictions = "Dietary restrictions: ${userData.dietaryRestrictions!.join(', ')}"; + } + return DefaultPage( - backflag: true, - reverse: true, - child: - Container( - alignment: Alignment.center, - height: screenHeight * 0.78, - padding: const EdgeInsets.fromLTRB(35, 20, 35, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - - IDCheckInHeader(_eventIDController), - QREnlarged(onPressed: () async { - final String id = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR); + backflag: true, + reverse: true, + child: Container( + alignment: Alignment.center, + height: screenHeight * 0.78, + padding: const EdgeInsets.fromLTRB(35, 20, 35, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IDCheckInHeader(_eventIDController), + Text( + dietaryRestrictions, + style: Theme.of(context) + .textTheme + .displaySmall + ?.copyWith(color: const Color(0xFFF7F1E2)), + ), + QREnlarged( + onPressed: () async { + final String id = await FlutterBarcodeScanner.scanBarcode( + '#ff6666', 'Cancel', true, ScanMode.QR); if (!["-1", "", null].contains(id)) { _eventIDController.value = TextEditingValue(text: id); } - },) - ], - )) - ); + }, + ) + ], + ))); } } class IDCheckInHeader extends StatelessWidget { - final TextEditingController eventIDController; const IDCheckInHeader(this.eventIDController); @@ -62,17 +97,19 @@ class IDCheckInHeader extends StatelessWidget { children: [ Text( "Check in with event ID", - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontSize: 20 - ), + style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 20), + ), + const SizedBox( + height: 10, ), - const SizedBox(height: 10,), TextField( controller: eventIDController, keyboardType: TextInputType.text, - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox( + height: 5, ), - const SizedBox(height: 5,), Align( alignment: Alignment.bottomRight, child: SolidButton( @@ -81,14 +118,17 @@ class IDCheckInHeader extends StatelessWidget { FocusScope.of(context).unfocus(); String snackBarText = ""; String id = eventIDController.text; - var model = Provider.of(context, listen: false); - if (id != null && id != "") { + var model = + Provider.of(context, listen: false); + if (id != "") { try { var contains = model.checkInItems.any((val) => val.id == id); if (!contains) { snackBarText = "Invalid item id"; } else { - String name = model.checkInItems.firstWhere((val) => val.id == id).name; + String name = model.checkInItems + .firstWhere((val) => val.id == id) + .name; // Confirmation dialog await showDialog( @@ -96,55 +136,65 @@ class IDCheckInHeader extends StatelessWidget { barrierDismissible: false, builder: (dialogContext) { bool isLoading = false; - return StatefulBuilder( - builder: (context, setState) { - return isLoading ? const Center( - child: CircularProgressIndicator( - strokeWidth: 2, - ), - ) : AlertDialog( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: const Text("Confirm Check In"), - content: RichText( - text: TextSpan( - text: "You are checking in to ", - children: [ - TextSpan( - text: "$name. \n\n", - style: const TextStyle(fontWeight: FontWeight.bold) - ), - const TextSpan(text: "Ensure that you have selected the correct event before confirming you attendance.") - ] + return StatefulBuilder(builder: (context, setState) { + return isLoading + ? const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ) + : AlertDialog( + backgroundColor: Theme.of(context) + .scaffoldBackgroundColor, + title: const Text("Confirm Check In"), + content: RichText( + text: TextSpan( + text: "You are checking in to ", + style: TextStyle( + color: Theme.of(context).primaryColor, ), - ), - actions: [ - TextButton( - child: const Text("Cancel"), - onPressed: () => Navigator.pop(dialogContext), - ), - TextButton( - child: const Text("Confirm"), - onPressed: () async { - try{ - setState(() {isLoading = true;}); - await model.selfCheckIn(id); - Navigator.pop(context); - snackBarText = "Checked in to $name!"; - eventIDController.clear(); - } on Exception catch (e) { - Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(e.toString().substring(11)), - )); - } - } - ) - ], - ); - } - ); - } - ); + children: [ + TextSpan( + text: "$name. \n\n", + style: const TextStyle( + fontWeight: + FontWeight.bold)), + const TextSpan( + text: + "Ensure that you have selected the correct event before confirming you attendance.") + ]), + ), + actions: [ + TextButton( + child: const Text("Cancel"), + onPressed: () => + Navigator.pop(dialogContext), + ), + TextButton( + child: const Text("Confirm"), + onPressed: () async { + try { + setState(() { + isLoading = true; + }); + await model.selfCheckIn(id); + Navigator.pop(context); + snackBarText = + "Checked in to $name!"; + eventIDController.clear(); + } on Exception catch (e) { + Navigator.pop(context); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text( + e.toString().substring(11)), + )); + } + }) + ], + ); + }); + }); } } on Exception { snackBarText = "Error checking in"; @@ -154,7 +204,7 @@ class IDCheckInHeader extends StatelessWidget { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(snackBarText), duration: const Duration(seconds: 1), - )); + )); } } } @@ -171,10 +221,9 @@ class IDCheckInHeader extends StatelessWidget { } class QREnlarged extends StatelessWidget { - final Function onPressed; - const QREnlarged({this.onPressed}); + const QREnlarged({required this.onPressed}); @override Widget build(BuildContext context) { @@ -186,25 +235,41 @@ class QREnlarged extends StatelessWidget { children: [ Text( "YOUR QR CODE", - style: Theme.of(context).textTheme.headline1.copyWith( - color: Theme.of(context).primaryColorLight - ), + style: Theme.of(context) + .textTheme + .displayLarge + ?.copyWith(color: Theme.of(context).primaryColorLight), ), - const SizedBox(height: 8,), + const SizedBox( + height: 8, + ), DecoratedBox( - decoration: BoxDecoration( - color: isLight?Theme.of(context).colorScheme.onPrimary:Theme.of(context).colorScheme.primary, - borderRadius: const BorderRadius.all(Radius.circular(16))), - child: Padding( - padding: const EdgeInsets.all(30), - child: QrImage( - data: id, - version: QrVersions.auto, - foregroundColor: isLight?Theme.of(context).accentColor:Theme.of(context).colorScheme.onPrimary, + decoration: BoxDecoration( + color: isLight + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.circular(16))), + child: Padding( + padding: const EdgeInsets.all(30), + child: QrImageView( + data: id, + version: QrVersions.auto, + eyeStyle: QrEyeStyle( + color: isLight + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.onPrimary, + ), + dataModuleStyle: QrDataModuleStyle( + color: isLight + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.onPrimary, ), - )), - const SizedBox(height: 15,), + ), + )), + const SizedBox( + height: 15, + ), // Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, // crossAxisAlignment: CrossAxisAlignment.center, @@ -225,6 +290,4 @@ class QREnlarged extends StatelessWidget { ], ); } -} - - +} \ No newline at end of file diff --git a/lib/pages/create_team.dart b/lib/pages/create_team.dart index 631f5de..910b5d9 100644 --- a/lib/pages/create_team.dart +++ b/lib/pages/create_team.dart @@ -23,12 +23,13 @@ class TeamTextField extends StatelessWidget { Widget build(BuildContext context) { return TextFormField( decoration: InputDecoration(labelText: label), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, controller: controller, validator: (val) { if (val == null || val.isEmpty) { return "Cannot be empty"; - } return null; + } + return null; }, ); } @@ -36,7 +37,7 @@ class TeamTextField extends StatelessWidget { class IsVisibleCheckBox extends StatelessWidget { final bool visibility; - final Function onTap; + final void Function(dynamic) onTap; const IsVisibleCheckBox(this.visibility, this.onTap); @@ -55,8 +56,6 @@ class IsVisibleCheckBox extends StatelessWidget { } } - - class CreateTeam extends StatefulWidget { @override _CreateTeamState createState() => _CreateTeamState(); @@ -76,8 +75,8 @@ class _CreateTeamState extends State { void getData() { hasTeam = Provider.of(context, listen: false).hasTeam; - if (hasTeam){ - Team team = Provider.of(context, listen: false).team; + if (hasTeam) { + Team team = Provider.of(context, listen: false).team!; visibility = team.visible; teamNameController.text = team.name; teamDescController.text = team.description; @@ -106,10 +105,9 @@ class _CreateTeamState extends State { final screenHeight = mqData.size.height; final screenWidth = mqData.size.width; return DefaultPage( - backflag: true, - reverse: true, - child: - Container( + backflag: true, + reverse: true, + child: Container( alignment: Alignment.center, padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), child: GradBox( @@ -119,82 +117,95 @@ class _CreateTeamState extends State { child: Form( key: _formKey, child: Column( - mainAxisAlignment: MainAxisAlignment - .spaceBetween, - crossAxisAlignment: CrossAxisAlignment - .start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 15,), - Text("TEAM INFO", style: - Theme.of(context).textTheme.headline1), - const SizedBox(height: 10), - Text("Basic Info", style: - Theme.of(context).textTheme.headline4), - const SizedBox(height: 15), - TeamTextField(teamNameController, "Team Name"), - const SizedBox(height: 20), - TeamTextField(teamDescController, "Team Desc"), - const SizedBox(height: 20), - IsVisibleCheckBox(visibility, (val) { - visibility = val; - setState(() {}); - }), - ], + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 15, + ), + Text("TEAM INFO", + style: + Theme.of(context).textTheme.displayLarge), + const SizedBox(height: 10), + Text("Basic Info", + style: + Theme.of(context).textTheme.headlineMedium), + const SizedBox(height: 15), + TeamTextField(teamNameController, "Team Name"), + const SizedBox(height: 20), + TeamTextField(teamDescController, "Team Desc"), + const SizedBox(height: 20), + IsVisibleCheckBox(visibility, (val) { + visibility = val ?? false; + setState(() {}); + }), + ], ), Container( alignment: Alignment.center, padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), child: SolidButton( - text: buttonText, - onPressed: () async { - if (_formKey.currentState.validate()) { - OverlayEntry loading = LoadingOverlay(context); - Overlay.of(context).insert(loading); - String token = Provider.of(context, listen: false).token; - String id = Provider.of(context, listen: false).id; - - String _teamName = teamNameController.text; - String _teamDesc = teamDescController.text; - - Response response = hasTeam ? await editTeam(_teamName, _teamDesc, visibility, token) - : await createTeam(_teamName, _teamDesc, visibility, token); - - if (response.statusCode != 200) { + text: buttonText, + onPressed: () async { + if (_formKey.currentState!.validate()) { + OverlayEntry loading = + loadingOverlay(context); + Overlay.of(context).insert(loading); + String token = Provider.of( + context, + listen: false) + .token; + String id = Provider.of( + context, + listen: false) + .id; + + String teamName = teamNameController.text; + String teamDesc = teamDescController.text; + + Response response = hasTeam + ? await editTeam(teamName, teamDesc, + visibility, token) + : await createTeam(teamName, teamDesc, + visibility, token); + + if (response.statusCode != 200) { + loading.remove(); + String errorMessage = jsonDecode( + response.body)["message"] ?? + "Failed to create team."; + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text(errorMessage), + )); + return; + } + + if (!hasTeam) { + await promoteToAdmin(id, token); + } + await Provider.of(context, + listen: false) + .fetchUserInfo(); loading.remove(); - String errorMessage = jsonDecode(response.body)["message"] ?? "Failed to create team."; - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(errorMessage), - )); - return; + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => ViewTeam(), + settings: const RouteSettings( + arguments: "", + )), + ); } - - if (!hasTeam) await promoteToAdmin(id, token); - await Provider.of(context, listen: false).fetchUserInfo(); - loading.remove(); - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => - ViewTeam(), - settings: const RouteSettings( - arguments: "", - ) - ), - ); - } - - }, - color: Theme.of(context).colorScheme.tertiaryContainer - ) - ) - ] - ), - ) - ) - ) - ); + }, + color: Theme.of(context) + .colorScheme + .tertiaryContainer)) + ]), + )))); } } diff --git a/lib/pages/edit_team.dart b/lib/pages/edit_team.dart index b32a07e..781fdf7 100644 --- a/lib/pages/edit_team.dart +++ b/lib/pages/edit_team.dart @@ -36,107 +36,91 @@ class OrangeButton extends StatelessWidget{ }*/ class _EditTeamState extends State { - - final List _teamMembers = [{'name': "Joyce Hong", 'email': "joyceh@andrew.cmu.edu"}, - {'name': "Joyce Hong", 'email': "joyceh@andrew.cmu.edu"}, - {'name': "Joyce Hong", 'email': "joyceh@andrew.cmu.edu"}]; + final List _teamMembers = [ + {'name': "Joyce Hong", 'email': "joyceh@andrew.cmu.edu"}, + {'name': "Joyce Hong", 'email': "joyceh@andrew.cmu.edu"}, + {'name': "Joyce Hong", 'email': "joyceh@andrew.cmu.edu"} + ]; final String _teamName = "My Team"; final String _teamDesc = "my team description"; bool read = true; - - Widget mailIconSelect(bool read){ - if (read){ - return Icon( - Icons.email, - color: Theme.of(context).colorScheme.secondary, - size: 40.0 - ); + Widget mailIconSelect(bool read) { + if (read) { + return Icon(Icons.email, + color: Theme.of(context).colorScheme.secondary, size: 40.0); } else { - return Icon( - Icons.mark_email_unread, - color: Theme.of(context).colorScheme.secondary, - size: 40.0 - ); + return Icon(Icons.mark_email_unread, + color: Theme.of(context).colorScheme.secondary, size: 40.0); } } Widget _buildTeamHeader(bool read) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("TEAM", style: Theme.of(context).textTheme.headline2), - mailIconSelect(read) - ] - ); + return Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text("TEAM", style: Theme.of(context).textTheme.displayMedium), + mailIconSelect(read) + ]); } Widget _buildTeamDesc() { return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(_teamName, style: Theme.of(context).textTheme.headline4), - Text(_teamDesc, style: Theme.of(context).textTheme.bodyText2) - ] - ); + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(_teamName, style: Theme.of(context).textTheme.headlineMedium), + Text(_teamDesc, style: Theme.of(context).textTheme.bodyMedium) + ]); } Widget _buildMember(int member) { - String emailStr = "(" + _teamMembers[member]['email'] + ")"; + String emailStr = "(${_teamMembers[member]['email']})"; return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(_teamMembers[member]['name'], style: Theme.of(context).textTheme.bodyText2), - Text(emailStr, style: Theme.of(context).textTheme.bodyText2) - ] - ); + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(_teamMembers[member]['name'], + style: Theme.of(context).textTheme.bodyMedium), + Text(emailStr, style: Theme.of(context).textTheme.bodyMedium) + ]); } Widget _buildTeamMembers() { return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Team Members", style: Theme.of(context).textTheme.headline4), - Column( - children: [ - _buildMember(0), - _buildMember(1), - _buildMember(2) - ] - ) - ] - ); + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Team Members", + style: Theme.of(context).textTheme.headlineMedium), + Column(children: [_buildMember(0), _buildMember(1), _buildMember(2)]) + ]); } - Widget _leaveTeamBtn() { - return ( - Container( + return (Container( alignment: Alignment.center, padding: const EdgeInsets.fromLTRB(20, 20, 20, 20), child: ElevatedButton( - style: ButtonStyle( - foregroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.secondary), - backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.secondary), - shadowColor: MaterialStateProperty.all(Theme.of(context).colorScheme.secondaryVariant), - shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))), - elevation: MaterialStateProperty.all(5), + style: ButtonStyle( + foregroundColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.secondary), + backgroundColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.secondary), + shadowColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.secondaryContainer), + shape: WidgetStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10))), + elevation: WidgetStateProperty.all(5), ), - onPressed: () { }, - child: Container( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), - child: Text("Leave Team", - style: TextStyle(fontSize:16.0, fontWeight: FontWeight.w600,color:Theme.of(context).colorScheme.onPrimary), - overflow: TextOverflow.fade, - softWrap: false - ) - ) - ) - ) - ); + onPressed: () {}, + child: Container( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), + child: Text("Leave Team", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onPrimary), + overflow: TextOverflow.fade, + softWrap: false))))); } @override @@ -146,54 +130,40 @@ class _EditTeamState extends State { final screenWidth = mqData.size.width; return DefaultPage( - backflag: true, - reverse: true, - child: - Container( + backflag: true, + reverse: true, + child: Container( alignment: Alignment.center, padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), child: GradBox( - width: screenWidth*0.9, - height: screenHeight*0.75, - padding: const EdgeInsets.fromLTRB(20, 5, 20, 5), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.fromLTRB(0, 10, 0, 10), - //height: screenHeight*0.05, - child: _buildTeamHeader(read) - ), - Container( - padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), - //height: screenHeight*0.2, - child: _buildTeamDesc() - ), - Container( - padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), - //height: screenHeight*0.05, - child: SolidButton( - text: "EDIT TEAM NAME AND INFO" - ) - ), - Container( - padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), - //height: screenHeight*0.1, - child: _buildTeamMembers() - ), - Container( - padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), - //height: screenHeight*0.1, - child: SolidButton( - text: "INVITE NEW MEMBER" - ) - ), - _leaveTeamBtn() - ] - ) - ) - ) - ); + width: screenWidth * 0.9, + height: screenHeight * 0.75, + padding: const EdgeInsets.fromLTRB(20, 5, 20, 5), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(0, 10, 0, 10), + //height: screenHeight*0.05, + child: _buildTeamHeader(read)), + Container( + padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), + //height: screenHeight*0.2, + child: _buildTeamDesc()), + Container( + padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), + //height: screenHeight*0.05, + child: SolidButton(text: "EDIT TEAM NAME AND INFO")), + Container( + padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), + //height: screenHeight*0.1, + child: _buildTeamMembers()), + Container( + padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), + //height: screenHeight*0.1, + child: SolidButton(text: "INVITE NEW MEMBER")), + _leaveTeamBtn() + ])))); } -} \ No newline at end of file +} diff --git a/lib/pages/editcheckinitem.dart b/lib/pages/editcheckinitem.dart index 5bce9e3..e885c8e 100644 --- a/lib/pages/editcheckinitem.dart +++ b/lib/pages/editcheckinitem.dart @@ -16,11 +16,11 @@ int daysBetween(DateTime from, DateTime to) { return (to.difference(from).inHours / 24).round(); } -double toDouble(TimeOfDay myTime) => myTime.hour + myTime.minute/60.0; +double toDouble(TimeOfDay myTime) => myTime.hour + myTime.minute / 60.0; // MAIN WIDGET class EditCheckInItemPage extends StatelessWidget { - final CheckInItem checkInItem; + final CheckInItem? checkInItem; const EditCheckInItemPage(this.checkInItem); @@ -31,25 +31,22 @@ class EditCheckInItemPage extends StatelessWidget { final screenWidth = mqData.size.width; return DefaultPage( - backflag: true, - reverse: true, - child: - Container( - alignment: Alignment.center, - height: screenHeight * 0.78, - padding: const EdgeInsets.fromLTRB(15, 20, 15, 0), - child: GradBox( - curvature: 20, - padding: const EdgeInsets.fromLTRB(20, 15, 20, 0), - child: CheckInItemForm(checkInItem), - ) - ) - ); + backflag: true, + reverse: true, + child: Container( + alignment: Alignment.center, + height: screenHeight * 0.78, + padding: const EdgeInsets.fromLTRB(15, 20, 15, 0), + child: GradBox( + curvature: 20, + padding: const EdgeInsets.fromLTRB(20, 15, 20, 0), + child: CheckInItemForm(checkInItem), + ))); } } class CheckInItemForm extends StatefulWidget { - final CheckInItem checkInItem; + final CheckInItem? checkInItem; const CheckInItemForm(this.checkInItem); @@ -59,9 +56,13 @@ class CheckInItemForm extends StatefulWidget { class _CheckInItemFormState extends State { final _formKey = GlobalKey(); - final List accessLevels = ["ALL", "SPONSORS_ONLY", - "PARTICIPANTS_ONLY", "ADMINS_ONLY"]; - + final List accessLevels = [ + "ALL", + "SPONSORS_ONLY", + "PARTICIPANTS_ONLY", + "ADMINS_ONLY" + ]; + final _nameController = TextEditingController(); final _descController = TextEditingController(); final _startDateController = TextEditingController(); @@ -70,41 +71,44 @@ class _CheckInItemFormState extends State { final _endTimeController = TextEditingController(); final _pointsController = TextEditingController(); - bool enableSelfCheckIn; - bool newItem; - String accessLevel; + late bool enableSelfCheckIn; + late bool newItem; + late String accessLevel; - DateTime startDate; - DateTime endDate; + DateTime? startDate; + DateTime? endDate; - TimeOfDay startTime; - TimeOfDay endTime; + TimeOfDay? startTime; + TimeOfDay? endTime; @override void initState() { super.initState(); - CheckInItem item = widget.checkInItem; - if (item!=null) { - _nameController.value = TextEditingValue(text: widget.checkInItem.name); - _descController.value = TextEditingValue(text: widget.checkInItem.description); - _pointsController.value = TextEditingValue(text: widget.checkInItem.points.toString()); - - startDate = DateTime.fromMicrosecondsSinceEpoch(widget.checkInItem.startTime); - endDate = DateTime.fromMicrosecondsSinceEpoch(widget.checkInItem.endTime); - startTime = TimeOfDay.fromDateTime(startDate); - endTime = TimeOfDay.fromDateTime(endDate); - - _startDateController.value = TextEditingValue(text: DateFormat.yMMMd('en_US').format(startDate)); - _startTimeController.value = TextEditingValue(text: DateFormat.Hm('en_US').format(startDate)); - _endDateController.value = TextEditingValue(text: DateFormat.yMMMd('en_US').format(endDate)); - _endTimeController.value = TextEditingValue(text: DateFormat.Hm('en_US').format(endDate)); + CheckInItem? item = widget.checkInItem; + if (item != null) { + _nameController.value = TextEditingValue(text: item.name); + _descController.value = TextEditingValue(text: item.description); + _pointsController.value = TextEditingValue(text: item.points.toString()); + + startDate = DateTime.fromMicrosecondsSinceEpoch(item.startTime); + endDate = DateTime.fromMicrosecondsSinceEpoch(item.endTime); + startTime = TimeOfDay.fromDateTime(startDate!); + endTime = TimeOfDay.fromDateTime(endDate!); + + _startDateController.value = + TextEditingValue(text: DateFormat.yMMMd('en_US').format(startDate!)); + _startTimeController.value = + TextEditingValue(text: DateFormat.Hm('en_US').format(startDate!)); + _endDateController.value = + TextEditingValue(text: DateFormat.yMMMd('en_US').format(endDate!)); + _endTimeController.value = + TextEditingValue(text: DateFormat.Hm('en_US').format(endDate!)); newItem = false; enableSelfCheckIn = item.enableSelfCheckIn; accessLevel = item.accessLevel; - } - else { + } else { newItem = true; enableSelfCheckIn = false; accessLevel = accessLevels[0]; @@ -113,8 +117,7 @@ class _CheckInItemFormState extends State { @override Widget build(BuildContext context) { - - var editable = Provider.of(context).isAdmin; + bool editable = Provider.of(context).isAdmin ?? false; return SingleChildScrollView( child: Form( key: _formKey, @@ -122,83 +125,89 @@ class _CheckInItemFormState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - - widget.checkInItem == null ? "NEW CHECKIN ITEM" - : editable ? "EDIT CHECKIN ITEM" : "EVENT DETAILS", - style: Theme.of(context).textTheme.headline1, + widget.checkInItem == null + ? "NEW CHECKIN ITEM" + : editable + ? "EDIT CHECKIN ITEM" + : "EVENT DETAILS", + style: Theme.of(context).textTheme.displayLarge, + ), + const SizedBox( + height: 20, ), - const SizedBox(height: 20,), // Form Fields EditCheckInFormField( label: "Name", controller: _nameController, + onTap: () {}, + validator: (String? val) { + return null; + }, ), EditCheckInFormField( label: "Description", controller: _descController, + onTap: () {}, + validator: (String? val) { + return null; + }, ), EditCheckInFormField( label: "Start Date", controller: _startDateController, onTap: () async { FocusScope.of(context).requestFocus(FocusNode()); - final DateTime picked = await showDatePicker( - context: context, - initialDate: startDate ?? DateTime.now(), - firstDate: startDate ?? DateTime.now(), - lastDate: DateTime(2024), - builder: (context, child) => Theme( - data: Theme.of(context).copyWith( - dialogBackgroundColor: Theme.of(context).colorScheme.background - ), - child: child - ) - ); + final DateTime? picked = await showDatePicker( + context: context, + initialDate: startDate ?? DateTime.now(), + firstDate: startDate ?? DateTime.now(), + lastDate: DateTime(2030), + builder: (context, child) => Theme( + data: Theme.of(context).copyWith( + dialogBackgroundColor: + Theme.of(context).colorScheme.surface), + child: child!)); if (picked != null) { _startDateController.value = TextEditingValue( - text: DateFormat.yMMMd('en_US').format(picked) - ); + text: DateFormat.yMMMd('en_US').format(picked)); startDate = picked; } }, + validator: (String? val) { + return null; + }, ), EditCheckInFormField( - label: "End Date", controller: _endDateController, validator: (val) { if (val == null || val.isEmpty) { return 'Cannot be empty'; } - if (startDate!=null) { - if (daysBetween(startDate, endDate)<0) { - return 'End date must be after start date'; - } + if (startDate != null && + endDate != null && + daysBetween(startDate!, endDate!) < 0) { + return 'End date must be after start date'; } return null; }, onTap: () async { FocusScope.of(context).requestFocus(FocusNode()); - final DateTime picked = await showDatePicker( - context: context, - initialDate: endDate ?? DateTime.now(), - firstDate: endDate ?? DateTime.now(), - lastDate: DateTime(2024), - builder: (context, child) => Theme( - data: Theme.of(context).copyWith( - dialogBackgroundColor: Theme.of(context).colorScheme.background - ), - child: child - ) - ); - if (picked != null) { - _endDateController.value = TextEditingValue( - text: DateFormat.yMMMd('en_US').format(picked) - ); - endDate = picked; - } + final DateTime? picked = await showDatePicker( + context: context, + initialDate: endDate ?? DateTime.now(), + firstDate: endDate ?? DateTime.now(), + lastDate: DateTime(2030), + builder: (context, child) => Theme( + data: Theme.of(context).copyWith( + dialogBackgroundColor: + Theme.of(context).colorScheme.surface), + child: child!)); + _endDateController.value = TextEditingValue( + text: DateFormat.yMMMd('en_US').format(picked!)); + endDate = picked; }, ), @@ -207,149 +216,174 @@ class _CheckInItemFormState extends State { controller: _startTimeController, onTap: () async { FocusScope.of(context).requestFocus(FocusNode()); - TimeOfDay picked = await showTimePicker( + TimeOfDay? picked = await showTimePicker( context: context, + initialTime: startTime ?? TimeOfDay.now()); + _startTimeController.value = + TextEditingValue(text: picked!.format(context)); - initialTime: startTime ?? TimeOfDay.now() - ); - if (picked != null) { - _startTimeController.value = TextEditingValue( - text: picked.format(context) - ); - - startTime = picked; - } - } + startTime = picked; + }, + validator: (String? val) { + return null; + }, ), EditCheckInFormField( - label: "End Time", controller: _endTimeController, validator: (val) { if (val == null || val.isEmpty) { return 'Cannot be empty'; } - if (startDate!=null) { - if (daysBetween(startDate, endDate)==0) { - if (toDouble(startTime) > toDouble(endTime)) { - return 'End time must be after start time'; - } + if (startTime != null && + endTime != null && + startDate != null && + endDate != null && + daysBetween(startDate!, endDate!) == 0) { + if (toDouble(startTime!) > toDouble(endTime!)) { + return 'End time must be after start time'; } } return null; }, onTap: () async { FocusScope.of(context).requestFocus(FocusNode()); - TimeOfDay picked = await showTimePicker( + TimeOfDay? picked = await showTimePicker( context: context, - initialTime: endTime ?? TimeOfDay.now() - ); - if (picked != null) { - _endTimeController.value = TextEditingValue( - text: picked.format(context) - ); - endTime = picked; - } - } - ), + initialTime: endTime ?? TimeOfDay.now()); + _endTimeController.value = + TextEditingValue(text: picked!.format(context)); + endTime = picked; + }), EditCheckInFormField( label: "Points", controller: _pointsController, keyboardType: TextInputType.number, + onTap: () {}, + validator: (String? value) { + return null; + }, ), // Dropdown menus - if (editable) EditCheckInDropDownFormField( - items: accessLevels.asMap().map((i, label) => - MapEntry(i, DropdownMenuItem( + if (editable) + EditCheckInDropDownFormField( + items: accessLevels + .asMap() + .map((i, label) => MapEntry( + i, + DropdownMenuItem( value: label, child: Text(label), - ))).values.toList(), - label: "Access levels", - initial: accessLevel, - onChange: (val) { + ))) + .values + .toList(), + label: "Access levels", + initial: accessLevel, + onChange: (val) { setState(() { accessLevel = val; }); - }, - ), - if (editable) const SizedBox(height: 15,), + }, + ), + if (editable) + const SizedBox( + height: 15, + ), // Active toggle - if (editable) Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Self Check In Allowed", - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontWeight: FontWeight.bold + if (editable) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Self Check In Allowed", + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.bold), ), - ), - Transform.scale( - scale: 1.5, - child: Checkbox( - activeColor: Theme.of(context).colorScheme.primary, - side: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 2), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), - ), - - value: enableSelfCheckIn, - onChanged: (val){ - setState(() { - enableSelfCheckIn = val; - }); - }), - ) - ], - ), + Transform.scale( + scale: 1.5, + child: Checkbox( + activeColor: Theme.of(context).colorScheme.primary, + side: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + value: enableSelfCheckIn, + onChanged: (val) { + setState(() { + enableSelfCheckIn = val!; + }); + }), + ) + ], + ), // Submit button - if (editable) SizedBox( - width: double.infinity, - child: SolidButton( - text: "CONFIRM", - onPressed: () async { - if (_formKey.currentState.validate()) { - DateTime startDateTime = DateTime(startDate.year, startDate.month, startDate.day, startTime.hour, startTime.minute); - DateTime endDateTime = DateTime(endDate.year, endDate.month, endDate.day, endTime.hour, endTime.minute); - - CheckInItemDTO updatedItem = CheckInItemDTO( - name: _nameController.text, - description: _descController.text, - accessLevel: accessLevel, - startTime: startDateTime.toUtc().microsecondsSinceEpoch, - endTime: endDateTime.toUtc().microsecondsSinceEpoch, - enableSelfCheckIn: enableSelfCheckIn, - points: int.tryParse(_pointsController.text) - ); - - // TODO maybe add some loading indicator? - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const Center( - child: CircularProgressIndicator( - strokeWidth: 2, - ))); - if (newItem) { - await Provider.of(context, listen: false).addCheckInItem(updatedItem); - } else { - await Provider.of(context, listen: false).editCheckInItem(updatedItem, widget.checkInItem.id); + if (editable) + SizedBox( + width: double.infinity, + child: SolidButton( + text: "CONFIRM", + onPressed: () async { + if (_formKey.currentState!.validate()) { + DateTime startDateTime = DateTime( + startDate!.year, + startDate!.month, + startDate!.day, + startTime!.hour, + startTime!.minute); + DateTime endDateTime = DateTime( + endDate!.year, + endDate!.month, + endDate!.day, + endTime!.hour, + endTime!.minute); + + CheckInItemDTO updatedItem = CheckInItemDTO( + name: _nameController.text, + description: _descController.text, + accessLevel: accessLevel, + startTime: + startDateTime.toUtc().microsecondsSinceEpoch, + endTime: endDateTime.toUtc().microsecondsSinceEpoch, + enableSelfCheckIn: enableSelfCheckIn, + points: int.tryParse(_pointsController.text) ?? 0); + + // TODO maybe add some loading indicator? + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + ))); + if (newItem) { + await Provider.of(context, + listen: false) + .addCheckInItem(updatedItem); + } else { + await Provider.of(context, + listen: false) + .editCheckInItem( + updatedItem, widget.checkInItem!.id); + } + Navigator.pop(context); + Navigator.pop(context); } - Navigator.pop(context); - Navigator.pop(context); - } - }, + }, + ), ), - ), - const SizedBox(height: 15,) - + const SizedBox( + height: 15, + ) ], )), ); @@ -360,22 +394,19 @@ class EditCheckInFormField extends StatelessWidget { final TextEditingController controller; final String label; final TextInputType keyboardType; - final Function onTap; + final void Function()? onTap; - final Function validator; + final String? Function(String?)? validator; - const EditCheckInFormField({ - this.controller, - this.label, - this.keyboardType = TextInputType.text, - - this.onTap, - this.validator - }); + const EditCheckInFormField( + {required this.controller, + required this.label, + required this.onTap, + required this.validator, + this.keyboardType = TextInputType.text}); @override Widget build(BuildContext context) { - var editable = Provider.of(context).isAdmin; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -384,24 +415,24 @@ class EditCheckInFormField extends StatelessWidget { controller: controller, keyboardType: keyboardType, onTap: onTap, - enabled: editable, - validator: validator ?? (val) { - if (val == null || val.isEmpty) { - return 'Cannot be empty'; - } - return null; - }, + validator: validator ?? + (val) { + if (val == null || val.isEmpty) { + return 'Cannot be empty'; + } + return null; + }, enableSuggestions: false, - inputFormatters: keyboardType == TextInputType.number ? [ - FilteringTextInputFormatter.digitsOnly - ] : [], - decoration: InputDecoration( - labelText: label - ), - style: Theme.of(context).textTheme.bodyText2, + inputFormatters: keyboardType == TextInputType.number + ? [FilteringTextInputFormatter.digitsOnly] + : [], + decoration: InputDecoration(labelText: label), + style: Theme.of(context).textTheme.bodyMedium, ), - const SizedBox(height: 15,) + const SizedBox( + height: 15, + ) ], ); } @@ -412,9 +443,13 @@ class EditCheckInDropDownFormField extends StatelessWidget { final String label; final String initial; - final Function onChange; + final void Function(dynamic)? onChange; - const EditCheckInDropDownFormField({this.items, this.label, this.initial, this.onChange}); + const EditCheckInDropDownFormField( + {required this.items, + required this.label, + required this.initial, + required this.onChange}); @override Widget build(BuildContext context) { @@ -425,16 +460,18 @@ class EditCheckInDropDownFormField extends StatelessWidget { flex: 5, child: Text( label, - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontWeight: FontWeight.bold - ), + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.bold), ), ), - const SizedBox(width: 10,), + const SizedBox( + width: 10, + ), Expanded( flex: 8, child: DropdownButtonFormField( - onChanged: onChange, items: items, value: initial, @@ -443,9 +480,4 @@ class EditCheckInDropDownFormField extends StatelessWidget { ], ); } -} - - - - - +} \ No newline at end of file diff --git a/lib/pages/enter_prizes.dart b/lib/pages/enter_prizes.dart index f56db27..f617495 100644 --- a/lib/pages/enter_prizes.dart +++ b/lib/pages/enter_prizes.dart @@ -7,43 +7,42 @@ import '../api.dart'; import '../models/prize.dart'; class EnterPrizes extends StatefulWidget { - - String projId; - List enteredPrizes; - EnterPrizes({this.projId, this.enteredPrizes}); + final String projId; + final List enteredPrizes; + const EnterPrizes({required this.projId, required this.enteredPrizes}); @override - _EnterPrizesState createState() => _EnterPrizesState(projId: projId, enteredPrizes: enteredPrizes); + EnterPrizesState createState() => EnterPrizesState(); } -class _EnterPrizesState extends State { - - SharedPreferences prefs; - bool isAdmin; - String id; - String token; - String projId; - - List prizes; - List enteredPrizes; +class EnterPrizesState extends State { + late bool isAdmin; + late String id; + late String token; + late String projId; + List prizes = []; + List enteredPrizes = []; - _EnterPrizesState({this.projId, this.enteredPrizes}); + @override + void initState() { + super.initState(); + projId = widget.projId; + enteredPrizes = widget.enteredPrizes; + getData(); + } - void getData() async{ - prefs = await SharedPreferences.getInstance(); + void getData() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); - isAdmin = prefs.getBool('admin'); - id = prefs.getString('id'); - token = prefs.getString('token'); + isAdmin = prefs.getBool('admin') ?? false; + id = prefs.getString('id') ?? ""; + token = prefs.getString('token') ?? ""; prizes = await getPrizes(); - setState(() { - - }); + setState(() {}); } - void prizeDialog(String prizeId) { showDialog( context: context, @@ -51,14 +50,17 @@ class _EnterPrizesState extends State { // return object of type Dialog return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Confirmation", style: Theme.of(context).textTheme.headline1), - content: Text("Are you sure you want to enter for this prize? This action cannot be undone.", style: Theme.of(context).textTheme.bodyText2), + title: Text("Confirmation", + style: Theme.of(context).textTheme.displayLarge), + content: Text( + "Are you sure you want to enter for this prize? This action cannot be undone.", + style: Theme.of(context).textTheme.bodyMedium), actions: [ // usually buttons at the bottom of the dialog TextButton( child: Text( "Cancel", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -67,7 +69,7 @@ class _EnterPrizesState extends State { TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { prizeEntry(prizeId); @@ -84,18 +86,10 @@ class _EnterPrizesState extends State { bool success = await enterPrize(context, projId, prizeId, token); if (success) { enteredPrizes.add(prizeId); - setState(() { - - }); + setState(() {}); } } - @override - void initState() { - getData(); - super.initState(); - } - @override Widget build(BuildContext context) { final mqData = MediaQuery.of(context); @@ -103,108 +97,104 @@ class _EnterPrizesState extends State { final screenWidth = mqData.size.width; return DefaultPage( - backflag: true, - reverse: true, - child: - Column( + backflag: true, + reverse: true, + child: Column( children: [ Container( - height: screenHeight*0.15, + height: screenHeight * 0.15, width: screenWidth, padding: const EdgeInsets.fromLTRB(25, 10, 0, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("ENTER FOR PRIZE", - style: Theme.of(context).textTheme.headline1, + Text( + "ENTER FOR PRIZE", + style: Theme.of(context).textTheme.displayLarge, ), - Text("Scroll to see the full list.", - style: Theme.of(context).textTheme.bodyText2, + Text( + "Scroll to see the full list.", + style: Theme.of(context).textTheme.bodyMedium, ) ], - ) - ), - if (prizes == null) - const SizedBox( - height: 100, - child: Center(child: CircularProgressIndicator()) - ) - else + )), Container( alignment: Alignment.center, padding: const EdgeInsets.fromLTRB(25, 0, 25, 0), child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: screenHeight*0.65 - ), + constraints: BoxConstraints(maxHeight: screenHeight * 0.65), child: ListView.builder( itemCount: prizes.length, - itemBuilder: (BuildContext context, int index){ + itemBuilder: (BuildContext context, int index) { return PrizeCard( id: prizes[index].id, name: prizes[index].name, - desc: prizes[index].description, entered: enteredPrizes.contains(prizes[index].id), - entryFn: () => prizeDialog(prizes[index].id,), + entryFn: () => prizeDialog( + prizes[index].id, + ), ); }, ), - ) - ) + )) ], - ) - ); + )); } } -class PrizeCard extends StatelessWidget{ +class PrizeCard extends StatelessWidget { String id; String name; String desc; bool entered; - Function entryFn; + void Function() entryFn; - PrizeCard({this.id, this.name, this.desc, this.entered, this.entryFn}); + PrizeCard( + {required this.id, + required this.name, + required this.desc, + required this.entered, + required this.entryFn}); @override - Widget build(BuildContext context){ + Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), - child: GradBox( - width: 100, - height: 200, - alignment: Alignment.topLeft, - padding: const EdgeInsets.fromLTRB(20, 15, 20, 15), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text(name, - style: Theme.of(context).textTheme.headline2, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - Text(desc, - style: Theme.of(context).textTheme.bodyText2, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - (entered) ? - SolidButton( - text: " Submitted ", - color: Colors.grey, - textColor: Colors.white, - onPressed: null, - ) - : SolidButton( - text: " Submit ", - onPressed: entryFn, - ) - ], - ) - ) - ); + padding: const EdgeInsets.fromLTRB(0, 8, 0, 8), + child: GradBox( + width: 100, + height: 200, + alignment: Alignment.topLeft, + padding: const EdgeInsets.fromLTRB(20, 15, 20, 15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + name, + style: Theme.of(context).textTheme.displayMedium, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + desc, + style: Theme.of(context).textTheme.bodyMedium, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + (entered) + ? SolidButton( + text: " Submitted ", + color: Colors.grey, + textColor: Colors.white, + onPressed: null, + ) + : SolidButton( + text: " Submit ", + onPressed: entryFn, + ) + ], + ))); } } diff --git a/lib/pages/events/edit.dart b/lib/pages/events/edit.dart index 90e0199..7eecd32 100644 --- a/lib/pages/events/edit.dart +++ b/lib/pages/events/edit.dart @@ -16,14 +16,14 @@ int daysBetween(DateTime from, DateTime to) { return (to.difference(from).inHours / 24).round(); } -double toDouble(TimeOfDay myTime) => myTime.hour + myTime.minute/60.0; +double toDouble(TimeOfDay myTime) => myTime.hour + myTime.minute / 60.0; // MAIN WIDGET class EditEventPage extends StatelessWidget { - final Event event; + final Event? event; final bool editable; - const EditEventPage(this.event, {this.editable=false}); + const EditEventPage(this.event, {this.editable = false}); @override Widget build(BuildContext context) { @@ -32,20 +32,17 @@ class EditEventPage extends StatelessWidget { final screenWidth = mqData.size.width; return DefaultPage( - backflag: true, - reverse: true, - child: - Container( - alignment: Alignment.center, - height: screenHeight * 0.78, - padding: const EdgeInsets.fromLTRB(15, 20, 15, 0), - child: GradBox( - curvature: 20, - padding: const EdgeInsets.fromLTRB(20, 15, 20, 0), - child: EventItemForm(event, editable), - ) - ) - ); + backflag: true, + reverse: true, + child: Container( + alignment: Alignment.center, + height: screenHeight * 0.78, + padding: const EdgeInsets.fromLTRB(15, 20, 15, 0), + child: GradBox( + curvature: 20, + padding: const EdgeInsets.fromLTRB(20, 15, 20, 0), + child: EventItemForm(event!, editable), + ))); } } @@ -60,7 +57,6 @@ class EventItemForm extends StatefulWidget { } class _EventFormState extends State { - void _showDialog(String response, String title, bool result) { // flutter defined function showDialog( @@ -73,52 +69,76 @@ class _EventFormState extends State { actions: [ // usually buttons at the bottom of the dialog TextButton( - child: const Text( - "OK", - style: TextStyle(color: Colors.white), - ), style: TextButton.styleFrom( - // foregroundColor: const Color.fromARGB(255, 255, 75, 43), - ), + // foregroundColor: const Color.fromARGB(255, 255, 75, 43), + ), onPressed: () { Navigator.of(context).pop(); - if(result == true){ + if (result == true) { Navigator.push( context, - MaterialPageRoute( - builder: (context) => EventsHomeScreen()), + MaterialPageRoute(builder: (context) => EventsHomeScreen()), ); } }, + child: const Text( + "OK", + style: TextStyle(color: Colors.white), + ), ), ], ); }, ); } + void addData(Map newEvent) async { - bool result = await addEvent(newEvent["name"], newEvent["description"], newEvent["startTime"], newEvent["endTime"], newEvent["location"], 0, 0, newEvent["platform"], newEvent["platformUrl"]); + bool result = await addEvent( + newEvent["name"] as String, + newEvent["description"] as String, + newEvent["startTime"] as int, + newEvent["endTime"] as int, + newEvent["location"] as String, + 0, + 0, + newEvent["platform"] as String, + newEvent["platformUrl"] as String); if (result == true) { _showDialog('Your event was successfully saved!', 'Success', result); - }else{ + } else { _showDialog('There was an error. Please try again.', 'Error.', result); } } void editData(Map newEvent) async { - bool result = await editEvent(newEvent["id"], newEvent["name"], newEvent["description"], newEvent["startTime"], newEvent["endTime"], newEvent["location"], 0, 0, newEvent["platform"], newEvent["platformUrl"]); + bool result = await editEvent( + newEvent["id"] as String, + newEvent["name"] as String, + newEvent["description"] as String, + newEvent["startTime"] as int, + newEvent["endTime"] as int, + newEvent["location"] as String, + 0, + 0, + newEvent["platform"] as String, + newEvent["platformUrl"] as String); if (result == true) { _showDialog('Your event was successfully saved!', 'Success', result); - }else{ + } else { _showDialog('There was an error. Please try again.', 'Error.', result); } } final _formKey = GlobalKey(); - final List platforms = ["IN_PERSON", "ZOOM", - "HOPIN", "DISCORD", "OTHER"]; + final List platforms = [ + "IN_PERSON", + "ZOOM", + "HOPIN", + "DISCORD", + "OTHER" + ]; final _nameController = TextEditingController(); final _descController = TextEditingController(); @@ -129,43 +149,46 @@ class _EventFormState extends State { final _endDateController = TextEditingController(); final _endTimeController = TextEditingController(); - bool newItem; - String platform; + late bool newItem; + late String platform; - DateTime startDate; - DateTime endDate; + late DateTime startDate; + late DateTime endDate; - TimeOfDay startTime; - TimeOfDay endTime; + late TimeOfDay startTime; + late TimeOfDay endTime; @override void initState() { super.initState(); Event item = widget.event; - if (item!=null) { - _nameController.value = TextEditingValue(text: widget.event.name); - _descController.value = TextEditingValue(text: widget.event.description); - _linkController.value = TextEditingValue(text: widget.event.platformUrl); - _locController.value = TextEditingValue(text: widget.event.location); - - startDate = DateTime.fromMicrosecondsSinceEpoch(widget.event.startTime); - endDate = DateTime.fromMicrosecondsSinceEpoch(widget.event.endTime); - startTime = TimeOfDay.fromDateTime(startDate); - endTime = TimeOfDay.fromDateTime(endDate); - - _startDateController.value = TextEditingValue(text: DateFormat.yMMMd('en_US').format(startDate)); - _startTimeController.value = TextEditingValue(text: DateFormat.Hm('en_US').format(startDate)); - _endDateController.value = TextEditingValue(text: DateFormat.yMMMd('en_US').format(endDate)); - _endTimeController.value = TextEditingValue(text: DateFormat.Hm('en_US').format(endDate)); - - newItem = false; - platform = item.platform; - } - else { + _nameController.value = TextEditingValue(text: widget.event.name); + _descController.value = TextEditingValue(text: widget.event.description ?? ""); + _linkController.value = TextEditingValue(text: widget.event.platformUrl ?? ""); + _locController.value = TextEditingValue(text: widget.event.location); + + startDate = DateTime.fromMicrosecondsSinceEpoch(widget.event.startTime); + endDate = DateTime.fromMicrosecondsSinceEpoch(widget.event.endTime); + startTime = TimeOfDay.fromDateTime(startDate); + endTime = TimeOfDay.fromDateTime(endDate); + + _startDateController.value = + TextEditingValue(text: DateFormat.yMMMd('en_US').format(startDate)); + _startTimeController.value = + TextEditingValue(text: DateFormat.Hm('en_US').format(startDate)); + _endDateController.value = + TextEditingValue(text: DateFormat.yMMMd('en_US').format(endDate)); + _endTimeController.value = + TextEditingValue(text: DateFormat.Hm('en_US').format(endDate)); + + if (widget.event.id == "") { newItem = true; - platform = platforms[0]; + } else { + newItem = false; } + + platform = item.platform; } @override @@ -178,12 +201,16 @@ class _EventFormState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - - widget.event == null ? "NEW EVENT" - : editable ? "EDIT EVENT" : "EVENT DETAILS", - style: Theme.of(context).textTheme.headline1, + widget.event == null + ? "NEW EVENT" + : editable + ? "EDIT EVENT" + : "EVENT DETAILS", + style: Theme.of(context).textTheme.displayLarge, + ), + const SizedBox( + height: 20, ), - const SizedBox(height: 20,), // Form Fields EditEventFormField( @@ -196,29 +223,39 @@ class _EventFormState extends State { controller: _descController, editable: editable, ), - if (editable) EditEventDropDownFormField( - items: platforms.asMap().map((i, label) => - MapEntry(i, DropdownMenuItem( - value: label, - child: Text(label), - ))).values.toList(), - label: "Platform", - initial: platform, - onChange: (val) { - setState(() { - platform = val; - }); - }, + if (editable) + EditEventDropDownFormField( + items: platforms + .asMap() + .map((i, label) => MapEntry( + i, + DropdownMenuItem( + value: label, + child: Text(label), + ))) + .values + .toList(), + label: "Platform", + initial: platform, + onChange: (val) { + setState(() { + platform = val; + }); + }, + ), + const SizedBox( + height: 20, ), - const SizedBox(height: 20,), EditEventFormField( label: "Location", controller: _locController, editable: editable, - validator: (String value) { - if (value.isEmpty && platform == "IN_PERSON") { + validator: (value) { + if (value == null || + value.isEmpty && platform == "IN_PERSON") { return "Location is required"; } + return null; }, ), @@ -226,10 +263,12 @@ class _EventFormState extends State { label: "Event URL", controller: _linkController, editable: editable, - validator: (String value) { - if (value.isEmpty && platform != "IN_PERSON") { + validator: (value) { + if (value == null || + value.isEmpty && platform != "IN_PERSON") { return "URL is required"; } + return null; }, ), @@ -239,23 +278,19 @@ class _EventFormState extends State { editable: editable, onTap: () async { FocusScope.of(context).requestFocus(FocusNode()); - final DateTime picked = await showDatePicker( + final DateTime? picked = await showDatePicker( context: context, initialDate: startDate ?? DateTime.now(), firstDate: startDate ?? DateTime.now(), - lastDate: DateTime(DateTime.now().year+1), + lastDate: DateTime(DateTime.now().year + 1), ); - if (picked != null) { - _startDateController.value = TextEditingValue( - text: DateFormat.yMMMd('en_US').format(picked) - ); + _startDateController.value = TextEditingValue( + text: DateFormat.yMMMd('en_US').format(picked!)); - startDate = picked; - } + startDate = picked; }, ), EditEventFormField( - label: "End Date", controller: _endDateController, editable: editable, @@ -263,27 +298,23 @@ class _EventFormState extends State { if (val == null || val.isEmpty) { return 'Cannot be empty'; } - if (startDate!=null) { - if (daysBetween(startDate, endDate)<0) { - return 'End date must be after start date'; - } + if (daysBetween(startDate, endDate) < 0) { + return 'End date must be after start date'; } + return null; }, onTap: () async { FocusScope.of(context).requestFocus(FocusNode()); - final DateTime picked = await showDatePicker( + final DateTime? picked = await showDatePicker( context: context, initialDate: endDate ?? DateTime.now(), firstDate: endDate ?? DateTime.now(), - lastDate: DateTime(DateTime.now().year+1), + lastDate: DateTime(DateTime.now().year + 1), ); - if (picked != null) { - _endDateController.value = TextEditingValue( - text: DateFormat.yMMMd('en_US').format(picked) - ); - endDate = picked; - } + _endDateController.value = TextEditingValue( + text: DateFormat.yMMMd('en_US').format(picked!)); + endDate = picked; }, ), @@ -293,22 +324,15 @@ class _EventFormState extends State { editable: editable, onTap: () async { FocusScope.of(context).requestFocus(FocusNode()); - TimeOfDay picked = await showTimePicker( + TimeOfDay? picked = await showTimePicker( context: context, + initialTime: startTime ?? TimeOfDay.now()); + _startTimeController.value = + TextEditingValue(text: picked!.format(context)); - initialTime: startTime ?? TimeOfDay.now() - ); - if (picked != null) { - _startTimeController.value = TextEditingValue( - text: picked.format(context) - ); - - startTime = picked; - } - } - ), + startTime = picked; + }), EditEventFormField( - label: "End Time", controller: _endTimeController, editable: editable, @@ -316,81 +340,95 @@ class _EventFormState extends State { if (val == null || val.isEmpty) { return 'Cannot be empty'; } - if (startDate!=null) { - if (daysBetween(startDate, endDate)==0) { - if (toDouble(startTime) > toDouble(endTime)) { - return 'End time must be after start time'; - } + if (daysBetween(startDate, endDate) == 0) { + if (toDouble(startTime) > toDouble(endTime)) { + return 'End time must be after start time'; } } + return null; }, onTap: () async { FocusScope.of(context).requestFocus(FocusNode()); - TimeOfDay picked = await showTimePicker( + TimeOfDay? picked = await showTimePicker( context: context, - initialTime: endTime ?? TimeOfDay.now() - ); - if (picked != null) { - _endTimeController.value = TextEditingValue( - text: picked.format(context) - ); - endTime = picked; - } - } - ), + initialTime: endTime ?? TimeOfDay.now()); + _endTimeController.value = + TextEditingValue(text: picked!.format(context)); + endTime = picked; + }), // Dropdown menus - - if (editable) const SizedBox(height: 15,), + if (editable) + const SizedBox( + height: 15, + ), // Submit button - if (editable) SizedBox( - width: double.infinity, - child: SolidButton( - text: "CONFIRM", - onPressed: () async { - if (_formKey.currentState.validate()) { - DateTime startDateTime = DateTime(startDate.year, startDate.month, startDate.day, startTime.hour, startTime.minute); - DateTime endDateTime = DateTime(endDate.year, endDate.month, endDate.day, endTime.hour, endTime.minute); - - - // TODO maybe add some loading indicator? - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const Center( - child: CircularProgressIndicator( - strokeWidth: 2, - ))); - if (newItem) { - addData({"name": _nameController.text, - "description": _descController.text, - "startTime": startDateTime.toUtc().microsecondsSinceEpoch, - "endTime": endDateTime.toUtc().microsecondsSinceEpoch, - "platform": platform, - "platformUrl": _linkController.text, - "location": _locController.text}); - } else { - editData({ - "id": widget.event.id, - "name": _nameController.text, - "description": _descController.text, - "startTime": startDateTime.toUtc().microsecondsSinceEpoch, - "endTime": endDateTime.toUtc().microsecondsSinceEpoch, - "platform": platform, - "platformUrl": _linkController.text, - "location": _locController.text}); + if (editable) + SizedBox( + width: double.infinity, + child: SolidButton( + text: "CONFIRM", + onPressed: () async { + if (_formKey.currentState!.validate()) { + DateTime startDateTime = DateTime( + startDate.year, + startDate.month, + startDate.day, + startTime.hour, + startTime.minute); + DateTime endDateTime = DateTime( + endDate.year, + endDate.month, + endDate.day, + endTime.hour, + endTime.minute); + + // TODO maybe add some loading indicator? + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + ))); + if (newItem) { + addData({ + "name": _nameController.text, + "description": _descController.text, + "startTime": + startDateTime.toUtc().microsecondsSinceEpoch, + "endTime": + endDateTime.toUtc().microsecondsSinceEpoch, + "platform": platform, + "platformUrl": _linkController.text, + "location": _locController.text + }); + } else { + editData({ + "id": widget.event.id, + "name": _nameController.text, + "description": _descController.text, + "startTime": + startDateTime.toUtc().microsecondsSinceEpoch, + "endTime": + endDateTime.toUtc().microsecondsSinceEpoch, + "platform": platform, + "platformUrl": _linkController.text, + "location": _locController.text + }); + } + Navigator.pop(context); } - Navigator.pop(context); - } - }, + }, + ), ), - ), - const SizedBox(height: 15,) - + const SizedBox( + height: 15, + ) ], )), ); @@ -401,22 +439,20 @@ class EditEventFormField extends StatelessWidget { final TextEditingController controller; final String label; final TextInputType keyboardType; - final Function onTap; + final void Function()? onTap; final bool editable; - final Function validator; + final String? Function(String?)? validator; - const EditEventFormField({ - this.controller, - this.label, - this.keyboardType = TextInputType.text, - this.onTap, - this.editable, - this.validator - }); + const EditEventFormField( + {required this.controller, + required this.label, + this.onTap, + required this.editable, + this.validator, + this.keyboardType = TextInputType.text}); @override Widget build(BuildContext context) { - return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -424,24 +460,25 @@ class EditEventFormField extends StatelessWidget { controller: controller, keyboardType: keyboardType, onTap: onTap, - enabled: editable, - validator: validator ?? (val) { - if (val == null || val.isEmpty) { - return 'Cannot be empty'; - } - return null; - }, + validator: validator ?? + (val) { + if (val == null || val.isEmpty) { + return 'Cannot be empty'; + } + + return null; + }, enableSuggestions: false, - inputFormatters: keyboardType == TextInputType.number ? [ - FilteringTextInputFormatter.digitsOnly - ] : [], - decoration: InputDecoration( - labelText: label - ), - style: Theme.of(context).textTheme.bodyText2, + inputFormatters: keyboardType == TextInputType.number + ? [FilteringTextInputFormatter.digitsOnly] + : [], + decoration: InputDecoration(labelText: label), + style: Theme.of(context).textTheme.bodyMedium, ), - const SizedBox(height: 15,) + const SizedBox( + height: 15, + ) ], ); } @@ -452,9 +489,13 @@ class EditEventDropDownFormField extends StatelessWidget { final String label; final String initial; - final Function onChange; + final void Function(dynamic) onChange; - const EditEventDropDownFormField({this.items, this.label, this.initial, this.onChange}); + const EditEventDropDownFormField( + {required this.items, + required this.label, + required this.initial, + required this.onChange}); @override Widget build(BuildContext context) { @@ -465,16 +506,19 @@ class EditEventDropDownFormField extends StatelessWidget { flex: 5, child: Text( label, - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontWeight: FontWeight.bold - ), + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.bold), ), ), - const SizedBox(width: 10,), + const SizedBox( + width: 10, + ), Expanded( flex: 8, child: DropdownButtonFormField( - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, dropdownColor: Theme.of(context).colorScheme.surface, onChanged: onChange, items: items, @@ -485,8 +529,3 @@ class EditEventDropDownFormField extends StatelessWidget { ); } } - - - - - diff --git a/lib/pages/events/index.dart b/lib/pages/events/index.dart index 1f54ea5..333c760 100644 --- a/lib/pages/events/index.dart +++ b/lib/pages/events/index.dart @@ -17,7 +17,7 @@ class EventsHomeScreen extends StatefulWidget { } class _EventsHomeScreenState extends State { - SharedPreferences prefs; + late SharedPreferences prefs; bool isAdmin = false; @@ -35,8 +35,8 @@ class _EventsHomeScreenState extends State { getData() async { SharedPreferences prefs = await SharedPreferences.getInstance(); - isAdmin = prefs.getBool("admin"); - isSponsor = prefs.getString("company") != null; + isAdmin = prefs.getBool("admin")!; + isSponsor = false; // prefs.getString("company") != ""; setState(() {}); } @@ -71,16 +71,14 @@ class _EventsHomeScreenState extends State { } String formatDate(String unixDate) { - var date = - DateTime.fromMillisecondsSinceEpoch(int.parse(unixDate) * 1000); + var date = DateTime.fromMillisecondsSinceEpoch(int.parse(unixDate) * 1000); date = date.toLocal(); String formattedDate = DateFormat('EEE dd MMM').format(date); return formattedDate.toUpperCase(); } String getTime(String unixDate) { - var date = - DateTime.fromMillisecondsSinceEpoch(int.parse(unixDate) * 1000); + var date = DateTime.fromMillisecondsSinceEpoch(int.parse(unixDate) * 1000); date = date.toLocal(); String formattedDate = DateFormat('hh:mm a').format(date); return formattedDate; @@ -124,7 +122,7 @@ class _EventsHomeScreenState extends State { ), tooltip: 'Zoom Link!', color: const Color.fromARGB(255, 37, 130, 242), - onPressed: () => launch('${data.zoom_link}')); + onPressed: () => launchUrl(Uri.parse(data.zoom_link))); } else if (data.access_code == 1) { return IconButton( icon: const Icon( @@ -134,7 +132,7 @@ class _EventsHomeScreenState extends State { ), tooltip: 'Zoom Link!', color: const Color.fromARGB(255, 37, 130, 242), - onPressed: () => launch('${data.zoom_link}')); + onPressed: () => launchUrl(Uri.parse(data.zoom_link))); } else { return IconButton( icon: Image.asset( @@ -145,7 +143,7 @@ class _EventsHomeScreenState extends State { ), tooltip: 'Zoom Link!', color: const Color.fromARGB(255, 37, 130, 242), - onPressed: () => launch('${data.zoom_link}')); + onPressed: () => launchUrl(Uri.parse(data.zoom_link))); } } @@ -160,7 +158,7 @@ class _EventsHomeScreenState extends State { color: const Color.fromARGB(255, 37, 130, 242), onPressed: () { String text = 'Join ${data.name} at ' '${data.zoom_link}'; - final RenderBox box = context.findRenderObject(); + final RenderBox box = context.findRenderObject() as RenderBox; Share.share(text, sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size); }, @@ -177,59 +175,83 @@ class _EventsHomeScreenState extends State { child: //create new event button Column(mainAxisAlignment: MainAxisAlignment.start, children: [ - SolidButton( - text: viewPast ? "View Upcoming Events" : "View Past Events", - onPressed: () { - setState(() { - viewPast = !viewPast; - }); - }, + SolidButton( + text: viewPast ? "View Upcoming Events" : "View Past Events", + onPressed: () { + setState(() { + viewPast = !viewPast; + }); + }, + ), + const SizedBox(height: 5), + if (isAdmin) + GradBox( + width: screenWidth * 0.9, + height: 60, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EditEventPage( + Event( + id: "", + name: "", + description: "", + location: "", + platform: "IN_PERSON", + platformUrl: "", + startTime: DateTime.now().millisecondsSinceEpoch, + endTime: DateTime.now().millisecondsSinceEpoch, + active: true, + lat: 0, + lng: 0), + editable: isAdmin, + )), + ); + }, + child: Text( + "CREATE NEW EVENT", + style: Theme.of(context).textTheme.displayMedium, ), - const SizedBox(height: 5), - if (isAdmin) - GradBox( - width: screenWidth * 0.9, - height: 60, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => EditEventPage(null, editable: isAdmin,)), + ), + Container( + alignment: Alignment.center, + padding: const EdgeInsets.fromLTRB(25, 10, 25, 5), + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: screenHeight * 0.6), + child: FutureBuilder( + future: getEvents(), + builder: (context, eventsSnapshot) { + if (eventsSnapshot.connectionState == + ConnectionState.waiting) { + return ListView.builder( + itemCount: 1, + itemBuilder: (BuildContext context, int index) { + return const Center(child: CircularProgressIndicator()); + }, + ); + } else if (eventsSnapshot.hasError) { + return ListView.builder( + itemCount: 1, + itemBuilder: (BuildContext context, int index) { + return const Center(child: Text("Error")); + }, + ); + } + + var data = eventsSnapshot.data as dynamic; + + return ListView.builder( + itemCount: (data[viewPast ? 1 : 0]).length, + itemBuilder: (BuildContext context, int index) { + return EventsCard( + (data[viewPast ? 1 : 0])[index], isAdmin); + }, ); }, - child: Text( - "CREATE NEW EVENT", - style: Theme.of(context).textTheme.headline2, - ), ), - Container( - alignment: Alignment.center, - padding: const EdgeInsets.fromLTRB(25, 10, 25, 5), - child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: screenHeight * 0.6), - child: FutureBuilder( - future: getEvents(), - builder: (context, eventsSnapshot) { - if (eventsSnapshot.data == null || - eventsSnapshot.hasData == null) { - return ListView.builder( - itemCount: 1, - itemBuilder: (BuildContext context, int index) { - return const Center(child: CircularProgressIndicator()); - }, - ); - } - return ListView.builder( - itemCount: (eventsSnapshot.data[viewPast?1:0]).length, - itemBuilder: (BuildContext context, int index) { - return EventsCard( - (eventsSnapshot.data[viewPast?1:0])[index], isAdmin); - }, - ); - }, - ), - )) - ]), + )) + ]), ); } } @@ -258,7 +280,7 @@ class PlaceHolder extends StatelessWidget { width: screenWidth * 0.5, child: Text( "Loading", - style: Theme.of(context).textTheme.headline2, + style: Theme.of(context).textTheme.displayMedium, maxLines: 1, overflow: TextOverflow.ellipsis, )), @@ -296,15 +318,15 @@ class EventsCard extends StatelessWidget { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: Text("Confirmation", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), content: Text("Are you sure you want to delete this event?", - style: Theme.of(context).textTheme.bodyText2), + style: Theme.of(context).textTheme.bodyMedium), actions: [ // usually buttons at the bottom of the dialog TextButton( child: Text( "Cancel", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -313,7 +335,7 @@ class EventsCard extends StatelessWidget { TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { deleteEvent(event.id); @@ -330,9 +352,15 @@ class EventsCard extends StatelessWidget { } _launchLink(BuildContext context) async { - String url = event.platformUrl; - if (await canLaunch(url)) { - await launch(url); + String? url = event.platformUrl; + + if (url == null) { + errorDialog(context, "Warning", "No link available for this event."); + return; + } + + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); } else { errorDialog(context, "Error", 'Could not launch event url.'); } @@ -354,7 +382,7 @@ class EventsCard extends StatelessWidget { children: [ Text( event.name, - style: Theme.of(context).textTheme.headline2, + style: Theme.of(context).textTheme.displayMedium, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -364,39 +392,39 @@ class EventsCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ SizedBox( - width: screenWidth*0.5, + width: screenWidth * 0.5, height: 160, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - event.description, - style: Theme.of(context).textTheme.bodyText2, + event.description ?? "No Description.", + style: Theme.of(context).textTheme.bodyMedium, maxLines: 2, overflow: TextOverflow.ellipsis, ), if (event.platform == 'IN_PERSON') Text( - "IN PERSON: " + event.location, - style: Theme.of(context).textTheme.bodyText2, + "IN PERSON: ${event.location}", + style: Theme.of(context).textTheme.bodyMedium, overflow: TextOverflow.ellipsis, ) else - Text( - "${event.platform}:", - style: Theme.of(context).textTheme.bodyText2, - overflow: TextOverflow.ellipsis, - ), - if(event.platformUrl!="") + Text( + "${event.platform}:", + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.ellipsis, + ), + if (event.platformUrl != "") SolidButton( - child: Icon(Icons.link, - color: + child: Icon(Icons.link, + color: Theme.of(context).colorScheme.onPrimary, - size: 35), - onPressed: () { - _launchLink(context); - }, + size: 35), + onPressed: () { + _launchLink(context); + }, ), if (isAdmin) Row( @@ -409,7 +437,10 @@ class EventsCard extends StatelessWidget { context, MaterialPageRoute( builder: (context) => - EditEventPage(event, editable: isAdmin,)), + EditEventPage( + event, + editable: isAdmin, + )), ); }, ), @@ -418,7 +449,9 @@ class EventsCard extends StatelessWidget { ), SolidButton( text: "Delete", - color: Theme.of(context).colorScheme.tertiaryContainer, + color: Theme.of(context) + .colorScheme + .tertiaryContainer, onPressed: () => confirmDialog(context), ), ]) @@ -429,8 +462,10 @@ class EventsCard extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => - EditEventPage(event, editable: isAdmin,)), + builder: (context) => EditEventPage( + event, + editable: isAdmin, + )), ); }, ), @@ -446,29 +481,24 @@ class EventsCard extends StatelessWidget { height: 160, child: Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, + color: + Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(10)), alignment: Alignment.center, child: Text( - getTime((event.startTime).toString()) + - "\n" + - formatDate( - (event.startTime).toString()), + "${getTime((event.startTime).toString())}\n${formatDate((event.startTime).toString())}", style: Theme.of(context) .textTheme - .headline2 - .copyWith( - color: Theme.of(context) - .colorScheme - .onPrimary), + .displayMedium + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onPrimary), textAlign: TextAlign.center))) - ] - ) + ]) ], ) ], - ) - ) - ); + ))); } } diff --git a/lib/pages/events/new.dart b/lib/pages/events/new.dart index 8e9f87b..d97c441 100644 --- a/lib/pages/events/new.dart +++ b/lib/pages/events/new.dart @@ -12,7 +12,6 @@ class NewEventScreen extends StatefulWidget { } class _NewEventScreenState extends State { - String _eventName = ""; String _eventDesc = ""; String _eventUrl = ""; @@ -23,7 +22,7 @@ class _NewEventScreenState extends State { int _endTime = 0; DateTime pickedDate = DateTime.now(); TimeOfDay pickedTime = TimeOfDay.now(); - + TextEditingController nameController = TextEditingController(); TextEditingController descController = TextEditingController(); TextEditingController locationController = TextEditingController(); @@ -34,7 +33,13 @@ class _NewEventScreenState extends State { var dropdownValue = 'IN_PERSON'; - final List _dropdownItems = ['IN_PERSON', "ZOOM", "DISCORD", "HOPIN", "OTHER"]; + final List _dropdownItems = [ + 'IN_PERSON', + "ZOOM", + "DISCORD", + "HOPIN", + "OTHER" + ]; TimeOfDay selectedStartTime = const TimeOfDay(hour: 00, minute: 00); TimeOfDay selectedEndTime = const TimeOfDay(hour: 00, minute: 00); @@ -42,7 +47,7 @@ class _NewEventScreenState extends State { final GlobalKey _formKey = GlobalKey(); @override - void dispose(){ + void dispose() { nameController.dispose(); descController.dispose(); locationController.dispose(); @@ -54,37 +59,42 @@ class _NewEventScreenState extends State { } void saveData() async { - bool result = await addEvent(_eventName, _eventDesc, _startTime, _endTime, _location, 0, 0, _platform, _eventUrl); + bool result = await addEvent(_eventName, _eventDesc, _startTime, _endTime, + _location, 0, 0, _platform, _eventUrl); if (result == true) { _showDialog('Your event was successfully saved!', 'Success', result); - }else{ + } else { _showDialog('There was an error. Please try again.', 'Error.', result); } } - void updateDate(){ + void updateDate() { DateTime combined = DateTime(pickedDate.year, pickedDate.month, pickedDate.day, pickedTime.hour, pickedTime.minute); setState(() { - _startTime = (combined.millisecondsSinceEpoch/1000).round(); - _endTime = (combined.add(Duration(minutes: _duration)).millisecondsSinceEpoch/1000).round(); + _startTime = (combined.millisecondsSinceEpoch / 1000).round(); + _endTime = + (combined.add(Duration(minutes: _duration)).millisecondsSinceEpoch / + 1000) + .round(); }); } - bool dateInRange(DateTime d, DateTime start, DateTime end){ + bool dateInRange(DateTime d, DateTime start, DateTime end) { return start.isBefore(d) && end.isAfter(d); } Future pickDate(BuildContext context) async { - DateTime sDate = DateTime(DateTime.now().year-5); - DateTime eDate = DateTime(DateTime.now().year+5); - final DateTime picked = await showDatePicker( + DateTime sDate = DateTime(DateTime.now().year - 5); + DateTime eDate = DateTime(DateTime.now().year + 5); + final DateTime? picked = await showDatePicker( context: context, - initialDate: dateInRange(pickedDate, sDate, eDate) ? pickedDate - : DateTime.now(), + initialDate: + dateInRange(pickedDate, sDate, eDate) ? pickedDate : DateTime.now(), firstDate: sDate, lastDate: eDate, ); + if (picked != null && picked != pickedDate) { setState(() { pickedDate = picked; @@ -95,10 +105,11 @@ class _NewEventScreenState extends State { } Future pickTime(BuildContext context) async { - final TimeOfDay picked = await showTimePicker( + final TimeOfDay? picked = await showTimePicker( context: context, initialTime: pickedTime, ); + if (picked != null && picked != pickedTime) { setState(() { pickedTime = picked; @@ -120,24 +131,22 @@ class _NewEventScreenState extends State { actions: [ // usually buttons at the bottom of the dialog TextButton( - child: const Text( - "OK", - style: TextStyle(color: Colors.white), - ), style: TextButton.styleFrom( - // foregroundColor: const Color.fromARGB(255, 255, 75, 43), - ), + // foregroundColor: const Color.fromARGB(255, 255, 75, 43), + ), onPressed: () { - Navigator.of(context).pop(); - if(result == true){ + if (result == true) { Navigator.push( context, - MaterialPageRoute( - builder: (context) => EventsHomeScreen()), + MaterialPageRoute(builder: (context) => EventsHomeScreen()), ); } }, + child: const Text( + "OK", + style: TextStyle(color: Colors.white), + ), ), ], ); @@ -148,13 +157,14 @@ class _NewEventScreenState extends State { Widget _buildName() { return TextFormField( decoration: const InputDecoration(labelText: "Event Name"), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, controller: nameController, - validator: (String value) { - if (value.isEmpty) { + validator: (value) { + if (value == null || value.isEmpty) { return 'Project name is required'; } - return null; + + throw Error(); }, onChanged: (String value) { _eventName = value; @@ -162,18 +172,17 @@ class _NewEventScreenState extends State { ); } - Widget _buildDesc() { return TextFormField( decoration: const InputDecoration(labelText: "Event Description"), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, controller: descController, - validator: (String value) { - if (value.isEmpty) { + validator: (value) { + if (value == null || value.isEmpty) { return 'Project description is required'; } - return null; + throw Error(); }, onChanged: (String value) { _eventDesc = value; @@ -184,13 +193,14 @@ class _NewEventScreenState extends State { Widget _buildLocation() { return TextFormField( decoration: const InputDecoration(labelText: "Location"), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, controller: locationController, - validator: (String value) { - if (value.isEmpty && _platform == "IN_PERSON") { + validator: (value) { + if (value == null || value.isEmpty && _platform == "IN_PERSON") { return "Location is required"; } - return null; + + throw Error(); }, onChanged: (String value) { _location = value; @@ -201,14 +211,14 @@ class _NewEventScreenState extends State { Widget _buildEventURL() { return TextFormField( decoration: const InputDecoration(labelText: "Event Link"), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, keyboardType: TextInputType.url, controller: linkController, - validator: (String value) { - if (value.isEmpty && _platform != "IN_PERSON") { + validator: (value) { + if (value == null || value.isEmpty && _platform != "IN_PERSON") { return "URL is required"; } - return null; + throw Error(); }, onChanged: (String value) { _eventUrl = value; @@ -218,49 +228,48 @@ class _NewEventScreenState extends State { Widget _buildDate() { return TextFormField( - decoration: const InputDecoration(labelText: "Date"), - style: Theme.of(context).textTheme.bodyText2, - controller: dateController, - validator: (String value) { - if (value.isEmpty) { - return 'Date is Required'; - } - return null; - }, - onTap: () { - pickDate(context); - } - ); + decoration: const InputDecoration(labelText: "Date"), + style: Theme.of(context).textTheme.bodyMedium, + controller: dateController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Date is Required'; + } + throw Error(); + }, + onTap: () { + pickDate(context); + }); } Widget _buildTime() { return TextFormField( - decoration: const InputDecoration(labelText: "Time"), - style: Theme.of(context).textTheme.bodyText2, - controller: timeController, - validator: (String value) { - if (value.isEmpty) { - return 'Time is Required'; - } - return null; - }, - onTap: () { - pickTime(context); - } - ); + decoration: const InputDecoration(labelText: "Time"), + style: Theme.of(context).textTheme.bodyMedium, + controller: timeController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Time is Required'; + } + throw Error(); + }, + onTap: () { + pickTime(context); + }); } Widget _buildDuration() { return TextFormField( decoration: const InputDecoration(labelText: "Duration (min)"), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, controller: durationController, keyboardType: TextInputType.number, - validator: (String value) { - if (value.isEmpty) { + validator: (value) { + if (value == null || value.isEmpty) { return 'Duration is Required'; } - return null; + + throw Error(); }, onChanged: (String value) { _duration = int.parse(value); @@ -269,37 +278,43 @@ class _NewEventScreenState extends State { ); } - Widget _meetingPlatformDropdown(width){ + Widget _meetingPlatformDropdown(width) { return Center( child: Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryVariant, - borderRadius: BorderRadius.circular(5) - ), + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(5)), padding: const EdgeInsets.only(left: 5, right: 5), - height: width*0.06, + height: width * 0.06, width: width * 0.4, child: DropdownButton( isExpanded: true, iconEnabledColor: Theme.of(context).colorScheme.primary, icon: const Icon(Icons.arrow_drop_down), iconSize: 25, - onChanged: (String val) { - setState(() { - _platform = val; - dropdownValue = val; - }); + onChanged: (val) { + if (val != null) { + setState(() { + _platform = val; + dropdownValue = val; + }); + } }, underline: const SizedBox(), value: dropdownValue, - items: _dropdownItems.map>((String val){return DropdownMenuItem(value: val, child: Text(val, style: Theme.of(context).textTheme.bodyText2,));}).toList() - , + items: _dropdownItems.map>((String val) { + return DropdownMenuItem( + value: val, + child: Text( + val, + style: Theme.of(context).textTheme.bodyMedium, + )); + }).toList(), ), ), ); } - @override Widget build(BuildContext context) { final mqData = MediaQuery.of(context); @@ -307,56 +322,55 @@ class _NewEventScreenState extends State { final screenWidth = mqData.size.width; return DefaultPage( - backflag: true, - reverse: true, - child: - Container( + backflag: true, + reverse: true, + child: Container( //color: Colors.black, - alignment: Alignment.center, - padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), - child: GradBox( - width: screenWidth*0.9, - height: screenHeight*0.75, - padding: const EdgeInsets.fromLTRB(15, 5, 15, 5), - child: Form( - key: _formKey, - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text("CREATE NEW EVENT", style: Theme.of(context).textTheme.headline2), - const SizedBox(height:45), - _buildName(), - const SizedBox(height:16), - _buildDesc(), - const SizedBox(height:16), - Center(child: Text("Meeting Platform", style: Theme.of(context).textTheme.headline4)), - const SizedBox(height:5), - _meetingPlatformDropdown(screenWidth), - const SizedBox(height:16), - _buildLocation(), - const SizedBox(height:16), - _buildEventURL(), - const SizedBox(height:16), - _buildDate(), - const SizedBox(height:16), - _buildTime(), - const SizedBox(height:16), - _buildDuration(), - const SizedBox(height:16), - Center( - child: SolidButton( - text: "Save information", - onPressed: saveData, - ), - ), - ], - ) - ) - ) - ) - ) - ); + alignment: Alignment.center, + padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), + child: GradBox( + width: screenWidth * 0.9, + height: screenHeight * 0.75, + padding: const EdgeInsets.fromLTRB(15, 5, 15, 5), + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("CREATE NEW EVENT", + style: Theme.of(context).textTheme.displayMedium), + const SizedBox(height: 45), + _buildName(), + const SizedBox(height: 16), + _buildDesc(), + const SizedBox(height: 16), + Center( + child: Text("Meeting Platform", + style: Theme.of(context) + .textTheme + .headlineMedium)), + const SizedBox(height: 5), + _meetingPlatformDropdown(screenWidth), + const SizedBox(height: 16), + _buildLocation(), + const SizedBox(height: 16), + _buildEventURL(), + const SizedBox(height: 16), + _buildDate(), + const SizedBox(height: 16), + _buildTime(), + const SizedBox(height: 16), + _buildDuration(), + const SizedBox(height: 16), + Center( + child: SolidButton( + text: "Save information", + onPressed: saveData, + ), + ), + ], + )))))); } -} \ No newline at end of file +} diff --git a/lib/pages/forgot.dart b/lib/pages/forgot.dart index 3676e9b..852b386 100644 --- a/lib/pages/forgot.dart +++ b/lib/pages/forgot.dart @@ -15,7 +15,7 @@ class Forgot extends StatefulWidget { } class _ForgotState extends State { - TextEditingController _emailcontroller; + late TextEditingController _emailcontroller; @override void initState() { @@ -30,7 +30,7 @@ class _ForgotState extends State { } void passwordRecovery(BuildContext context) async { - OverlayEntry loading = LoadingOverlay(context); + OverlayEntry loading = loadingOverlay(context); Overlay.of(context).insert(loading); bool success = await resetPassword(_emailcontroller.text); loading.remove(); @@ -48,7 +48,7 @@ class _ForgotState extends State { final mqData = MediaQuery.of(context); final screenHeight = mqData.size.height; final screenWidth = mqData.size.width; - var _themeProvider = Provider.of(context, listen: false); + var themeProvider = Provider.of(context, listen: false); return Scaffold( body: SingleChildScrollView( @@ -64,14 +64,14 @@ class _ForgotState extends State { color1: Theme.of(context).colorScheme.primary, color2: Theme.of(context) .colorScheme - .secondaryVariant), + .secondaryContainer), ), Container( height: screenHeight * 0.3, width: screenWidth, alignment: Alignment.topCenter, padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), - child: _themeProvider.getTheme == lightTheme + child: themeProvider.getTheme == lightTheme ? Image.asset("lib/logos/thLogoLight.png") : Image.asset("lib/logos/thLogoLight.png")) ]), @@ -89,7 +89,7 @@ class _ForgotState extends State { decoration: const InputDecoration( labelText: "Email", ), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, keyboardType: TextInputType.emailAddress, enableSuggestions: false, ), @@ -112,10 +112,9 @@ class _ForgotState extends State { children: [ Text("Already have an account?", style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.onBackground - ) - ), + fontSize: 14, + color: + Theme.of(context).colorScheme.onSurface)), TextButton( onPressed: () { Navigator.push( @@ -126,11 +125,10 @@ class _ForgotState extends State { }, child: Text("Try Logging In", style: TextStyle( - fontSize: 14, - color: Theme.of(context).colorScheme.tertiaryContainer - ) - ) - ), + fontSize: 14, + color: Theme.of(context) + .colorScheme + .tertiaryContainer))), ], ), const SizedBox(height: 5) diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 78bc1b3..e38b550 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -7,18 +7,17 @@ import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:thdapp/api.dart'; import 'package:thdapp/components/DefaultPage.dart'; -import 'package:thdapp/components/ErrorDialog.dart'; import 'package:thdapp/components/buttons/GradBox.dart'; import 'package:thdapp/components/buttons/SolidButton.dart'; -import 'package:thdapp/pages/team_api.dart'; import 'package:thdapp/pages/teams_list.dart'; +import 'package:thdapp/providers/expo_config_provider.dart'; import 'package:thdapp/providers/user_info_provider.dart'; +import 'package:thdapp/pages/admin/manage_tables.dart'; import 'package:url_launcher/url_launcher.dart'; import 'dart:math'; import '../models/discord.dart'; import '../models/profile.dart'; -import '../models/team.dart'; import 'checkin.dart'; import 'events/index.dart'; import 'leaderboard.dart'; @@ -30,14 +29,13 @@ class Home extends StatefulWidget { } class _HomeState extends State { - SharedPreferences prefs; - String token; + String token = ""; - DiscordInfo discordInfo; + DiscordInfo discordInfo = DiscordInfo(code: "0", expiry: "0", link: "0"); void getData() async { - prefs = await SharedPreferences.getInstance(); - token = prefs.getString('token'); + SharedPreferences prefs = await SharedPreferences.getInstance(); + token = prefs.getString('token') as String; Provider.of(context, listen: false).fetchUserInfo(); discordInfo = await getDiscordInfo(token); @@ -48,12 +46,15 @@ class _HomeState extends State { @override initState() { super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of(context, listen: false).loadConfig(token); + }); getData(); } _launchDiscord() async { String url = discordInfo.link; - launch(url); + launchUrl(Uri.parse(url)); } void discordVerifyDialog(BuildContext context) { @@ -69,16 +70,16 @@ class _HomeState extends State { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: Text("Verification Code", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( "When you join our Discord server, you'll be prompted to enter the following verification code by the Discord Bot running the server. This code will expire in 10 minutes.\n", - style: Theme.of(context).textTheme.bodyText2), + style: Theme.of(context).textTheme.bodyMedium), Text( - snapshot.data.code, - style: Theme.of(context).textTheme.headline3, + snapshot.data.hashCode.toString(), + style: Theme.of(context).textTheme.displaySmall, textAlign: TextAlign.center, ) ], @@ -87,17 +88,17 @@ class _HomeState extends State { TextButton( child: Text( "COPY", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { - Clipboard.setData( - ClipboardData(text: snapshot.data.code)); + Clipboard.setData(ClipboardData( + text: snapshot.data.hashCode.toString())); }, ), TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -109,15 +110,15 @@ class _HomeState extends State { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: - Text("Error", style: Theme.of(context).textTheme.headline1), + Text("Error", style: Theme.of(context).textTheme.displayLarge), content: Text( "We ran into an error while getting your Discord verification code", - style: Theme.of(context).textTheme.bodyText2), + style: Theme.of(context).textTheme.bodyMedium), actions: [ TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -129,7 +130,7 @@ class _HomeState extends State { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: Text("Verifying...", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), content: Container( alignment: Alignment.center, height: 70, @@ -141,14 +142,45 @@ class _HomeState extends State { ); } + Widget _buildAdminSection() { + return Consumer( + builder: (context, userInfo, child) { + if (userInfo.isAdmin) { + return Column( + children: [ + const SizedBox(height: 16), + Text( + "Admin Controls", + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 8), + SolidButton( + text: "Manage Project Tables", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ManageTablesPage(), + ), + ); + }, + ), + // ... any other admin buttons ... + ], + ); + } + return const SizedBox.shrink(); // Return empty widget for non-admins + }, + ); + } + @override Widget build(BuildContext context) { final mqData = MediaQuery.of(context); final screenHeight = mqData.size.height; final screenWidth = mqData.size.width; - Profile userData = Provider.of(context).userProfile; - Team userTeam = Provider.of(context).team; + Profile? userData = Provider.of(context).userProfile; Status status = Provider.of(context).userInfoStatus; bool hasTeam = Provider.of(context).hasTeam; @@ -173,20 +205,27 @@ class _HomeState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("HACKING TIME LEFT", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), const SizedBox(height: 8), CountdownTimer( - endTime: 1675549800000, - endWidget: Text("Time's up!", - style: TextStyle( - fontSize: 24.0, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.tertiary) + endTime: Provider.of(context) + .config + ?.submissionDeadline + .millisecondsSinceEpoch ?? + DateTime.now().millisecondsSinceEpoch, + endWidget: Text( + "Time's up!", + style: TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.tertiary + ) ), textStyle: TextStyle( - fontSize: 30.0, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.tertiary), + fontSize: 30.0, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.tertiary + ), ), ])), SizedBox(height: screenHeight * 0.08), @@ -196,11 +235,8 @@ class _HomeState extends State { alignment: Alignment.bottomCenter, padding: const EdgeInsets.fromLTRB(0, 0, 0, 10), child: Text( - "Swipe to see all the places where\n" + - String.fromCharCode($larr) + - "the hacking is happening" + - String.fromCharCode(($rarr)), - style: Theme.of(context).textTheme.bodyText1, + "Swipe to see all the places where\n${String.fromCharCode($larr)}the hacking is happening${String.fromCharCode(($rarr))}", + style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center, )), CarouselSlider( @@ -213,17 +249,14 @@ class _HomeState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( - "Hi " + - userData.firstName + - " " + - userData.lastName + - "!", + "Hi ${userData?.firstName} ${userData?.lastName}!", textAlign: TextAlign.center, - style: - Theme.of(context).textTheme.headline4), + style: Theme.of(context) + .textTheme + .headlineMedium), Text( "Welcome to TartanHacks!", - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center, ), SolidButton( @@ -256,13 +289,13 @@ class _HomeState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text("Swag Points", - style: - Theme.of(context).textTheme.headline4), + style: Theme.of(context) + .textTheme + .headlineMedium), Text( - "Points Earned: " + - userData.totalPoints.toString(), + "Points Earned: ${userData?.totalPoints}", style: - Theme.of(context).textTheme.bodyText2), + Theme.of(context).textTheme.bodyMedium), SolidButton( text: "Leaderboard", onPressed: () { @@ -294,11 +327,12 @@ class _HomeState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text("Discord Server", - style: - Theme.of(context).textTheme.headline4), + style: Theme.of(context) + .textTheme + .headlineMedium), Text( "Join the official TartanHacks Discord!", - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center, ), SolidButton( @@ -330,7 +364,7 @@ class _HomeState extends State { }, child: Text( "VIEW YOUR PROJECT", - style: Theme.of(context).textTheme.headline2, + style: Theme.of(context).textTheme.displayMedium, ), ) else @@ -345,9 +379,10 @@ class _HomeState extends State { }, child: Text( "JOIN A TEAM", - style: Theme.of(context).textTheme.headline2, + style: Theme.of(context).textTheme.displayMedium, ), - ) + ), + _buildAdminSection() ], )); } diff --git a/lib/pages/leaderboard.dart b/lib/pages/leaderboard.dart index 8c0b3a4..29b3b06 100644 --- a/lib/pages/leaderboard.dart +++ b/lib/pages/leaderboard.dart @@ -15,20 +15,25 @@ class Leaderboard extends StatefulWidget { } class _LeaderboardState extends State { - List lbData; - int selfRank; - Profile userData; - String token; + List lbData = []; + int selfRank = 0; + String displayName = ""; + int totalPoints = 0; + String token = ""; + bool loading = true; final TextEditingController _editNicknameController = TextEditingController(); void getData() async { SharedPreferences prefs = await SharedPreferences.getInstance(); - token = prefs.getString('token'); - String id = prefs.getString('id'); + token = prefs.getString('token') ?? ""; + String id = prefs.getString('id') ?? ""; lbData = await getLeaderboard(); selfRank = await getSelfRank(token); - userData = await getProfile(id, token); + Profile userData = await getProfile(id, token); + totalPoints = userData.totalPoints; + displayName = userData.displayName; + loading = false; setState(() {}); } @@ -41,17 +46,17 @@ class _LeaderboardState extends State { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: Text("Enter New Nickname", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), content: TextField( controller: _editNicknameController, - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ), actions: [ // usually buttons at the bottom of the dialog TextButton( child: Text( "Cancel", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () async { Navigator.of(context).pop(); @@ -60,19 +65,17 @@ class _LeaderboardState extends State { TextButton( child: Text( "Save", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () async { - OverlayEntry loading = LoadingOverlay(context); + OverlayEntry loading = loadingOverlay(context); Overlay.of(context).insert(loading); bool success = - await setDisplayName(_editNicknameController.text, token); + await setDisplayName(_editNicknameController.text, token) ?? + false; loading.remove(); - if (success == null) { - errorDialog( - context, "Error", "An error occurred. Please try again."); - } else if (success) { + if (success) { showDialog( context: context, builder: (BuildContext context) { @@ -81,15 +84,15 @@ class _LeaderboardState extends State { backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: Text("Success", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), content: Text("Nickname has been changed.", - style: Theme.of(context).textTheme.bodyText2), + style: Theme.of(context).textTheme.bodyMedium), actions: [ // usually buttons at the bottom of the dialog TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context) @@ -132,7 +135,7 @@ class _LeaderboardState extends State { return DefaultPage( backflag: true, - child: (userData == null) + child: (loading) ? const Center(child: CircularProgressIndicator()) : Container( alignment: Alignment.center, @@ -154,52 +157,56 @@ class _LeaderboardState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("YOUR POSITION:", - style: Theme.of(context).textTheme.headline3), + style: + Theme.of(context).textTheme.displaySmall), SolidButton( + color: Theme.of(context) + .colorScheme + .tertiaryContainer, + onPressed: _editNickname, child: Icon(Icons.edit, color: Theme.of(context) .colorScheme .onTertiaryContainer), - color: Theme.of(context).colorScheme.tertiaryContainer, - onPressed: _editNickname, ), ], ), LBRow( place: selfRank, - name: userData.displayName, - points: userData.totalPoints), + name: displayName, + points: totalPoints), ], ), ), GradBox( width: screenWidth * 0.9, height: screenHeight * 0.55, - padding: const EdgeInsets.fromLTRB(20, 5, 20, 5), + padding: const EdgeInsets.fromLTRB(20, 10, 20, 5), alignment: Alignment.topLeft, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("LEADERBOARD", - style: Theme.of(context).textTheme.headline1), + style: + Theme.of(context).textTheme.displayLarge), Text( "Scroll to see the whole board!", - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ), Expanded( child: Container( - alignment: Alignment.center, - child: ListView.builder( - itemCount: lbData.length, - itemBuilder: - (BuildContext context, int index) { - return LBRow( - place: lbData[index].rank, - name: lbData[index].displayName, - points: lbData[index].totalPoints); - }, - ), + alignment: Alignment.center, + child: ListView.builder( + itemCount: lbData.length, + itemBuilder: + (BuildContext context, int index) { + return LBRow( + place: lbData[index].rank, + name: lbData[index].displayName, + points: lbData[index].totalPoints); + }, + ), )) ])) ], @@ -212,7 +219,7 @@ class LBRow extends StatelessWidget { final String name; final int points; - const LBRow({this.place, this.name, this.points}); + const LBRow({required this.place, required this.name, required this.points}); @override Widget build(BuildContext context) { @@ -226,7 +233,7 @@ class LBRow extends StatelessWidget { alignment: Alignment.center, child: Text( place.toString(), - style: Theme.of(context).textTheme.headline1, + style: Theme.of(context).textTheme.displayLarge, ), ), Expanded(child: SolidButton(text: name, onPressed: null)), @@ -235,7 +242,7 @@ class LBRow extends StatelessWidget { alignment: Alignment.center, child: Text( "$points pts", - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, )) ], )); diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 4c43bbc..e5b97d2 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -24,7 +24,7 @@ class _LoginState extends State { final _emailcontroller = TextEditingController(); final _passwordcontroller = TextEditingController(); - SharedPreferences prefs; + late SharedPreferences prefs; @override initState() { @@ -40,9 +40,9 @@ class _LoginState extends State { } void login(String email, String password) async { - OverlayEntry loading = LoadingOverlay(context); + OverlayEntry loading = loadingOverlay(context); Overlay.of(context).insert(loading); - User logindata = await checkCredentials(email, password); + User? logindata = await checkCredentials(email, password); if (logindata != null) { TextInput.finishAutofillContext(); if (logindata.company != null) { @@ -51,13 +51,18 @@ class _LoginState extends State { context, MaterialPageRoute(builder: (ctxt) => Sponsors()), ); - // } else if (!logindata.admin && logindata.status != "CONFIRMED") { - // loading.remove(); - // errorDialog(context, "Unconfirmed", "Your participant account has not been " - // "confirmed and you are currently on the waitlist. \n\nYou may log into the dashboard " - // "after you've been confirmed."); + } else if (!logindata.admin && logindata.status != "CONFIRMED") { + loading.remove(); + errorDialog( + context, + "Unconfirmed", + "Your participant account has not been " + "confirmed and you are currently on the waitlist. \n\nYou may log into the dashboard " + "after you've been confirmed."); } else { - Provider.of(context, listen: false).fetchUserInfo().then((_) { + Provider.of(context, listen: false) + .fetchUserInfo() + .then((_) { loading.remove(); Navigator.pushReplacement( context, @@ -76,12 +81,14 @@ class _LoginState extends State { prefs = await SharedPreferences.getInstance(); if (prefs.getString('theme') == "light") { - var _themeProvider = Provider.of(context, listen: false); - _themeProvider.setTheme(lightTheme); + var themeProvider = Provider.of(context, listen: false); + themeProvider.setTheme(lightTheme); } - if (prefs.get('email') != null) { - User logindata = await checkCredentials(prefs.get('email'), prefs.get('password')); + if (prefs.getString('email') != null && + prefs.getString('password') != null) { + User? logindata = await checkCredentials( + prefs.getString('email')!, prefs.getString('password')!); if (logindata == null) { Provider.of(context, listen: false).reset(); prefs.clear(); @@ -90,12 +97,17 @@ class _LoginState extends State { context, MaterialPageRoute(builder: (ctxt) => Sponsors()), ); - // } else if (!logindata.admin && logindata.status != "CONFIRMED") { - // errorDialog(context, "Unconfirmed", "Your participant account has not been " - // "confirmed and you are currently on the waitlist. \n\nYou may log into the dashboard " - // "after you've been confirmed."); + } else if (!logindata.admin && logindata.status != "CONFIRMED") { + errorDialog( + context, + "Unconfirmed", + "Your participant account has not been " + "confirmed and you are currently on the waitlist. \n\nYou may log into the dashboard " + "after you've been confirmed."); } else { - Provider.of(context, listen: false).fetchUserInfo().then((_) { + Provider.of(context, listen: false) + .fetchUserInfo() + .then((_) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (ctxt) => Home()), @@ -110,7 +122,7 @@ class _LoginState extends State { final mqData = MediaQuery.of(context); final screenHeight = mqData.size.height; final screenWidth = mqData.size.width; - var _themeProvider = Provider.of(context, listen: false); + var themeProvider = Provider.of(context, listen: false); return Scaffold( body: SingleChildScrollView( @@ -124,16 +136,15 @@ class _LoginState extends State { size: Size(screenWidth, screenHeight * 0.45), painter: CurvedBottom( color1: Theme.of(context).colorScheme.primary, - color2: Theme.of(context) - .colorScheme - .secondary), + color2: Theme.of(context).colorScheme.secondary), ), Container( height: screenHeight * 0.3, width: screenWidth, alignment: Alignment.topCenter, - padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), - child: _themeProvider.getTheme == lightTheme + padding: EdgeInsets.fromLTRB( + 30, screenWidth * 0.2, 30, 0), + child: themeProvider.getTheme == lightTheme ? Image.asset("lib/logos/thLogoLight.png") : Image.asset("lib/logos/thLogoLight.png")) ]), @@ -145,7 +156,7 @@ class _LoginState extends State { color1: Theme.of(context).colorScheme.primary, color2: Theme.of(context) .colorScheme - .secondaryVariant)), + .secondaryContainer)), AutofillGroup( child: Column( children: [ @@ -157,7 +168,7 @@ class _LoginState extends State { decoration: const InputDecoration( labelText: "Email", ), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next), ), @@ -170,7 +181,7 @@ class _LoginState extends State { decoration: const InputDecoration( labelText: "Password", ), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ), ) ], @@ -198,8 +209,9 @@ class _LoginState extends State { }, child: Text("Forgot Password", style: TextStyle( - color: - Theme.of(context).colorScheme.tertiaryContainer))), + color: Theme.of(context) + .colorScheme + .tertiaryContainer))), ])))); } } diff --git a/lib/pages/old_bookmarks.dart b/lib/pages/old_bookmarks.dart index a028424..fa89f58 100644 --- a/lib/pages/old_bookmarks.dart +++ b/lib/pages/old_bookmarks.dart @@ -5,8 +5,16 @@ import 'package:thdapp/components/buttons/SolidButton.dart'; import 'package:thdapp/components/topbar/TopBar.dart'; class OldBookmarks extends StatelessWidget { - final List bookmarks = ["[Student A]", "[Student B]", "[Student C]", "[Student D]", - "[Student E]", "[Student F]", "[Student G]", "[Student F]"]; + final List bookmarks = [ + "[Student A]", + "[Student B]", + "[Student C]", + "[Student D]", + "[Student E]", + "[Student F]", + "[Student G]", + "[Student F]" + ]; final bool isProjects = true; @override @@ -18,85 +26,74 @@ class OldBookmarks extends StatelessWidget { return Scaffold( body: SingleChildScrollView( child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: screenHeight + constraints: BoxConstraints(maxHeight: screenHeight), + child: Column(mainAxisAlignment: MainAxisAlignment.end, children: [ + const TopBar(), + Stack(children: [ + Column(children: [ + SizedBox(height: screenHeight * 0.05), + CustomPaint( + size: Size(screenWidth, screenHeight * 0.75), + painter: CurvedTop( + color1: Theme.of(context).colorScheme.secondaryContainer, + color2: Theme.of(context).colorScheme.primary, + reverse: true)), + ] // children ), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - const TopBar(), - Stack( - children: [ - Column( - children:[ - SizedBox(height:screenHeight * 0.05), - CustomPaint( - size: Size(screenWidth, screenHeight * 0.75), - painter: CurvedTop( - color1: Theme.of(context).colorScheme.secondaryVariant, - color2: Theme.of(context).colorScheme.primary, - reverse: true) - ), - ] // children - ), - Column( - children: [ - SizedBox( - height: screenHeight * 0.80, - child: Column( - children: [ - Row( - children: [ - Container( - alignment: Alignment.topLeft, - padding: EdgeInsets.fromLTRB(screenWidth * 0.08, 0, screenWidth * 0.08, 0), - child: Text("BOOKMARKS", style: Theme.of(context).textTheme.headline2), - ), - Switch( - value: isProjects, - // onChanged: (value) { - // setState(() { - // isProjects = value; - // }); - // }, - activeTrackColor: const Color(0xFFF6C744), - activeColor: const Color(0xFFF68F44), - ), - ] - ), - Container( - alignment: Alignment.topLeft, - padding: EdgeInsets.fromLTRB(screenWidth * 0.08, 0, screenWidth * 0.08, 0), - child: Text("Scroll for the full list. Toggle for projects.", style: TextStyle(fontSize: screenHeight * 0.02)), - ), - Expanded( - child: Container( - padding: EdgeInsets.fromLTRB(screenWidth * 0.05, 0, screenWidth * 0.05, 0), - alignment: Alignment.topCenter, - child: ListView.builder( - itemCount: bookmarks.length, - itemBuilder: (BuildContext context, int index){ - return BookmarkInfo( - name: bookmarks[index], - team: "ScottyLabs", - bio: "[Bio]" - ); - }, - ), - ) - ) - ] - ) - ) - ], // children - ) - ] + Column( + children: [ + SizedBox( + height: screenHeight * 0.80, + child: Column(children: [ + Row(children: [ + Container( + alignment: Alignment.topLeft, + padding: EdgeInsets.fromLTRB( + screenWidth * 0.08, 0, screenWidth * 0.08, 0), + child: Text("BOOKMARKS", + style: Theme.of(context).textTheme.displayMedium), + ), + Switch( + value: isProjects, + // onChanged: (value) { + // setState(() { + // isProjects = value; + // }); + // }, + activeTrackColor: const Color(0xFFF6C744), + activeColor: const Color(0xFFF68F44), + onChanged: (bool value) { }, + ), + ]), + Container( + alignment: Alignment.topLeft, + padding: EdgeInsets.fromLTRB( + screenWidth * 0.08, 0, screenWidth * 0.08, 0), + child: Text( + "Scroll for the full list. Toggle for projects.", + style: TextStyle(fontSize: screenHeight * 0.02)), ), - ] - ), - ) - ) - ); + Expanded( + child: Container( + padding: EdgeInsets.fromLTRB( + screenWidth * 0.05, 0, screenWidth * 0.05, 0), + alignment: Alignment.topCenter, + child: ListView.builder( + itemCount: bookmarks.length, + itemBuilder: (BuildContext context, int index) { + return BookmarkInfo( + name: bookmarks[index], + team: "ScottyLabs", + bio: "[Bio]"); + }, + ), + )) + ])) + ], // children + ) + ]), + ]), + ))); } } @@ -105,65 +102,52 @@ class BookmarkInfo extends StatelessWidget { final String team; final String bio; - const BookmarkInfo({this.name, this.team, this.bio}); + const BookmarkInfo( + {required this.name, required this.team, required this.bio}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), child: GradBox( - alignment: Alignment.topLeft, - width: 200, - height: 180, - child: Row( - children: [ + alignment: Alignment.topLeft, + width: 200, + height: 180, + child: Row(children: [ Container( width: 230, padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - name, - style: Theme.of(context).textTheme.headline4 - ), - Text( - team, - style: Theme.of(context).textTheme.bodyText2 - ), - Text( - bio, - style: Theme.of(context).textTheme.bodyText2 - ), + Text(name, + style: Theme.of(context).textTheme.headlineMedium), + Text(team, style: Theme.of(context).textTheme.bodyMedium), + Text(bio, style: Theme.of(context).textTheme.bodyMedium), const SizedBox(height: 18), SolidButton( text: "View More", - onPressed: () { viewMorePopup(context, 'View More', 'hello'); }, ) - ] - ), + ]), ), RawMaterialButton( onPressed: null, elevation: 2.0, fillColor: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.all(12), + shape: const CircleBorder(), child: Icon( Icons.bookmark, size: 50.0, - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, ), - padding: const EdgeInsets.all(12), - shape: const CircleBorder(), ), - ] - ) - ), + ])), ); } - } void viewMorePopup(context, String title, String response) { @@ -173,13 +157,14 @@ void viewMorePopup(context, String title, String response) { builder: (BuildContext context) { // return object of type Dialog return AlertDialog( - title: Text(title, style: Theme.of(context).textTheme.headline1), - content: Text(response, style: Theme.of(context).textTheme.bodyText2), + title: Text(title, style: Theme.of(context).textTheme.displayLarge), + content: Text(response, style: Theme.of(context).textTheme.bodyMedium), actions: [ // usually buttons at the bottom of the dialog TextButton( - child: Text("Close", - style: Theme.of(context).textTheme.headline4, + child: Text( + "Close", + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -189,4 +174,4 @@ void viewMorePopup(context, String title, String response) { ); }, ); -} \ No newline at end of file +} diff --git a/lib/pages/profile_page.dart b/lib/pages/profile_page.dart index 63d22f0..cfe530f 100644 --- a/lib/pages/profile_page.dart +++ b/lib/pages/profile_page.dart @@ -18,7 +18,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:image_cropper/image_cropper.dart'; class ProfilePage extends StatefulWidget { - final Map bookmarks; + final Map? bookmarks; const ProfilePage({this.bookmarks}); @@ -27,57 +27,52 @@ class ProfilePage extends StatefulWidget { } class _ProfilePageState extends State { - - SharedPreferences prefs; + late SharedPreferences prefs; bool isAdmin = false; - String id; - String token; + late String id; + late String token; - Profile userData; - String teamName; + late Profile userData; + late String teamName; bool isSelf = false; - File profilePicFile; + late CroppedFile? profilePicFile; final _editNicknameController = TextEditingController(); + final ImagePicker _picker = ImagePicker(); - void getData() async{ + void getData() async { prefs = await SharedPreferences.getInstance(); - isAdmin = prefs.getBool('admin'); - token = prefs.getString('token'); - - if (id == null) { - id = prefs.getString('id'); - isSelf = true; - } + isAdmin = prefs.getBool('admin')!; + token = prefs.getString('token')!; + id = prefs.getString('id')!; + isSelf = true; userData = await getProfile(id, token); - Team userTeam = await getTeamById(id, token); + Team? userTeam = await getTeamById(id, token); if (userTeam != null) { teamName = userTeam.name; } else { teamName = "No team"; } - setState(() { - - }); + setState(() {}); } _launchResume() async { - String url = userData.resume; - if (await canLaunch(url)) { - await launch(url); + String? url = userData.resume; + if (url != null && await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); } else { errorDialog(context, "Error", 'Could not launch resume url.'); } } _launchGithub() async { - String url = "https://github.com/" + userData.github; - if (await canLaunch(url)) { - await launch(url); + String url = "https://github.com/${userData.github}"; + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); } else { errorDialog(context, "Error", 'Could not launch GitHub url.'); } @@ -91,18 +86,18 @@ class _ProfilePageState extends State { // return object of type Dialog return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Enter New Nickname", style: Theme.of(context).textTheme.headline1), + title: Text("Enter New Nickname", + style: Theme.of(context).textTheme.displayLarge), content: TextField( controller: _editNicknameController, - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ), actions: [ // usually buttons at the bottom of the dialog TextButton( child: Text( "Cancel", - style: Theme.of(context - ).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () async { Navigator.of(context).pop(); @@ -111,34 +106,38 @@ class _ProfilePageState extends State { TextButton( child: Text( "Save", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), - onPressed: () async{ - OverlayEntry loading = LoadingOverlay(context); + onPressed: () async { + OverlayEntry loading = loadingOverlay(context); Overlay.of(context).insert(loading); - bool success = await setDisplayName(_editNicknameController.text, token); + bool success = + await setDisplayName(_editNicknameController.text, token) ?? + false; loading.remove(); - if (success == null) { - errorDialog(context, "Error", "An error occurred. Please try again."); - } else if (success) { + if (success) { showDialog( context: context, builder: (BuildContext context) { // return object of type Dialog return AlertDialog( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Success", style: Theme.of(context).textTheme.headline1), - content: Text("Nickname has been changed.", style: Theme.of(context).textTheme.bodyText2), + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + title: Text("Success", + style: Theme.of(context).textTheme.displayLarge), + content: Text("Nickname has been changed.", + style: Theme.of(context).textTheme.bodyMedium), actions: [ // usually buttons at the bottom of the dialog TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { - Navigator.of(context).popUntil(ModalRoute.withName("profpage")); + Navigator.of(context) + .popUntil(ModalRoute.withName("profpage")); }, ), ], @@ -146,7 +145,8 @@ class _ProfilePageState extends State { }, ); } else { - errorDialog(context, "Nickname taken", "Please try a different name."); + errorDialog(context, "Nickname taken", + "Please try a different name."); } }, ), @@ -161,119 +161,142 @@ class _ProfilePageState extends State { showDialog( context: context, builder: (BuildContext context) { - return StatefulBuilder(builder: (context, setState) - { + return StatefulBuilder(builder: (context, setState) { return AlertDialog( - title: Text("Profile Picture:", style: Theme.of(context).textTheme.headline1), + title: Text("Profile Picture:", + style: Theme.of(context).textTheme.displayLarge), backgroundColor: Theme.of(context).scaffoldBackgroundColor, - content: Align(alignment: Alignment.center, widthFactor: 1, heightFactor: 1, + content: Align( + alignment: Alignment.center, + widthFactor: 1, + heightFactor: 1, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - ButtonBar(alignment: MainAxisAlignment.center, children: [ - SolidButton( - text: "Delete Uploaded Picture", - onPressed: () async { - if (Image.network(userData.profilePicture) != null) { - OverlayEntry loading = LoadingOverlay(context); - Overlay.of(context).insert(loading); - bool didUpload = await deleteProfilePic(token); - if (!didUpload) { - errorDialog(context, "Error", - "An error occurred. Please try again."); - } - else { - loading.remove(); - } - } - } - ) - ]), - AspectRatio(aspectRatio: 1.0 / 1.0, - child: profilePicFile != null ? Image.file(profilePicFile) : Container(color: Colors.black, child: Center(child:Text("No picture chosen", style: const TextStyle(color: Colors.white)))) - ), - ButtonBar(alignment: MainAxisAlignment.center, - children: [SolidButton( - text: "Gallery", - onPressed: () { - _getImage(ImageSource.gallery, setState); - } - ), + OverflowBar( + alignment: MainAxisAlignment.center, + children: [ SolidButton( - text: "Camera", - onPressed: () { - _getImage(ImageSource.camera, setState); - } - ), + text: "Delete Uploaded Picture", + onPressed: () async { + OverlayEntry loading = + loadingOverlay(context); + Overlay.of(context).insert(loading); + bool didUpload = + await deleteProfilePic(token); + if (!didUpload) { + errorDialog(context, "Error", + "An error occurred. Please try again."); + } else { + loading.remove(); + } + }) + ]), + AspectRatio( + aspectRatio: 1.0 / 1.0, + child: profilePicFile != null + ? Image.file(profilePicFile as File) + : Container( + color: Colors.black, + child: const Center( + child: Text("No picture chosen", + style: TextStyle( + color: Colors.white))))), + OverflowBar( + alignment: MainAxisAlignment.center, + children: [ SolidButton( - text: "Crop", - onPressed: () { - _cropPicture(setState); - } - ), - ] - ), - + text: "Gallery", + onPressed: () { + _getImage(ImageSource.gallery, setState); + }), + SolidButton( + text: "Camera", + onPressed: () { + _getImage(ImageSource.camera, setState); + }), + SolidButton( + text: "Crop", + onPressed: () { + _cropPicture(setState); + }), + ]), ], ), ), actions: [ TextButton( - child: Text("Cancel", - style: Theme.of(context).textTheme.headline4, + child: Text( + "Cancel", + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () async { Navigator.of(context).pop(); }, ), TextButton( - child: Text("Save", style: Theme.of(context).textTheme.headline4,), + child: Text( + "Save", + style: Theme.of(context).textTheme.headlineMedium, + ), onPressed: () async { if (profilePicFile != null) { - OverlayEntry loading = LoadingOverlay(context); + OverlayEntry loading = loadingOverlay(context); Overlay.of(context).insert(loading); bool didUpload = await uploadProfilePic( - profilePicFile, token); + File(profilePicFile!.path), token); if (!didUpload) { errorDialog(context, "Error", "An error occurred. Please try again."); - } - else { + } else { loading.remove(); } } - Navigator.of(context).popUntil(ModalRoute.withName( - "profpage")); + Navigator.of(context) + .popUntil(ModalRoute.withName("profpage")); }, ) - ] - ); + ]); }); - } - ).then((value) => getData()); + }).then((value) => getData()); } - _cropPicture(setState) async { + _cropPicture(setState) async { + if (userData.profilePicture != null) throw Error(); if (profilePicFile != null) { profilePicFile = await ImageCropper().cropImage( - sourcePath: profilePicFile != null ? profilePicFile.path: Uri.parse(userData.profilePicture).path, - aspectRatioPresets: [ - CropAspectRatioPreset.square, - ],); - setState((){ - }); - } - else { + sourcePath: profilePicFile != null + ? profilePicFile!.path + : Uri.parse(userData.profilePicture!).path, + uiSettings: [ + AndroidUiSettings( + aspectRatioPresets: [ + CropAspectRatioPreset.square, + ], + ), + IOSUiSettings( + aspectRatioPresets: [ + CropAspectRatioPreset.square, + ], + ), + WebUiSettings( + context: context, + ), + ], + ); + setState(() {}); + } else { errorDialog(context, "Error", 'No file uploaded from phone'); } - } - + } _getImage(ImageSource source, setState) async { - profilePicFile = await ImagePicker.pickImage(source: source); - setState((){ - }); + String? profilePicFilePath = + (await _picker.pickImage(source: source))?.path; + profilePicFile = + profilePicFilePath == null ? null : CroppedFile(profilePicFilePath); + setState(() {}); } @override @@ -294,18 +317,14 @@ class _ProfilePageState extends State { final screenHeight = mqData.size.height; final screenWidth = mqData.size.width; - if (ModalRoute.of(context) != null) { - id = ModalRoute - .of(context) - .settings - .arguments as String; + if (ModalRoute.of(context)?.settings.arguments != null) { + id = ModalRoute.of(context)?.settings.arguments as String; } return DefaultPage( - backflag: true, - reverse: true, - child: - Container( + backflag: true, + reverse: true, + child: Container( alignment: Alignment.center, padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), child: GradBox( @@ -315,169 +334,178 @@ class _ProfilePageState extends State { child: SizedBox( width: screenWidth * 0.9, height: screenHeight * 0.75, - child: - Column( + child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children:[ - Text("HACKER PROFILE", - style: Theme.of(context).textTheme.headline1, - ), - if (!isSelf && id != null) - Expanded( - child: IconButton( - icon: widget.bookmarks.containsValue(id) ? const Icon(Icons.bookmark) : const Icon(Icons.bookmark_outline), - color: Theme.of(context).colorScheme.primary, - iconSize: 40.0, - onPressed: () async { - if (widget.bookmarks.containsValue(id)) { - String bmId = widget.bookmarks.keys.firstWhere( - (k) => widget.bookmarks[k] == id, orElse: () => null); - deleteBookmark(token, bmId); - widget.bookmarks.remove(bmId); - } else { - String bmId = await addBookmark(token, id); - widget.bookmarks[bmId] = id; - } - setState(() { - - }); - } - ) - ) - ] - ), - if (userData == null) - const SizedBox( - height: 100, - child: Center(child: CircularProgressIndicator()) - ) - else - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 150, - padding: const EdgeInsets.fromLTRB( - 0, 10, 0, 10), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onTap: (){ - if (isSelf) { - _editPicture(); - } - }, - child: - AspectRatio(aspectRatio: 1/1, child: - ClipRRect( - borderRadius: BorderRadius - .circular(10), - child: - Container(color: Colors.black, child: Center(child: userData.profilePicture != null ? - Image.network(userData.profilePicture, fit: BoxFit.cover, errorBuilder:(BuildContext context, Object exception, StackTrace stackTrace) {return Image.asset('lib/logos/defaultpfp.PNG');}): Image.asset('lib/logos/defaultpfp.PNG')), - ) - ), - )), - const SizedBox(width: 25), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(userData.firstName, - style: Theme.of(context).textTheme.headline3 - ), - Text(userData.lastName, - style: Theme.of(context).textTheme.headline3 - ), - Text('"' + userData.displayName + '"', - style: Theme.of(context).textTheme.bodyText2 - ), - - Text(teamName, - style: Theme.of(context).textTheme.bodyText2 - ), - ], - ) - ) - ], - ) + Row(children: [ + Text( + "HACKER PROFILE", + style: Theme.of(context).textTheme.displayLarge, + ), + if (!isSelf) + Expanded( + child: IconButton( + icon: widget.bookmarks != null && + widget.bookmarks!.containsValue(id) + ? const Icon(Icons.bookmark) + : const Icon(Icons.bookmark_outline), + color: Theme.of(context).colorScheme.primary, + iconSize: 40.0, + onPressed: () async { + if (widget.bookmarks!.containsValue(id)) { + String bmId = widget.bookmarks?.keys + .firstWhere( + (k) => widget.bookmarks?[k] == id, + orElse: () => null); + deleteBookmark(token, bmId); + widget.bookmarks?.remove(bmId); + } else { + String bmId = + await addBookmark(token, id); + widget.bookmarks?[bmId] = id; + } + setState(() {}); + })) + ]), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 150, + padding: + const EdgeInsets.fromLTRB(0, 10, 0, 10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () { + if (isSelf) { + _editPicture(); + } + }, + child: AspectRatio( + aspectRatio: 1 / 1, + child: ClipRRect( + borderRadius: + BorderRadius.circular(10), + child: Container( + color: Colors.black, + child: Center( + child: userData + .profilePicture != + null + ? Image.network( + userData + .profilePicture!, + fit: BoxFit.cover, + errorBuilder: + (BuildContext + context, + Object + exception, + StackTrace? + stackTrace) { + return Image.asset( + 'lib/logos/defaultpfp.PNG'); + }) + : Image.asset( + 'lib/logos/defaultpfp.PNG')), + )), + )), + const SizedBox(width: 25), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text(userData.firstName, + style: Theme.of(context) + .textTheme + .displaySmall), + Text(userData.lastName, + style: Theme.of(context) + .textTheme + .displaySmall), + Text('"${userData.displayName}"', + style: Theme.of(context) + .textTheme + .bodyMedium), + Text(teamName, + style: Theme.of(context) + .textTheme + .bodyMedium), + ], + )) + ], + )), + if (isSelf) + SolidButton( + text: "Edit Nickname", + onPressed: _editNickname, ), - if (isSelf) - SolidButton( - text: "Edit Nickname", - onPressed: _editNickname, + const SizedBox(height: 10), + Text(userData.school, + style: + Theme.of(context).textTheme.displaySmall), + Text(userData.major ?? "", + style: Theme.of(context).textTheme.bodyMedium), + Text( + "Expected graduation ${userData.graduationYear}", + style: Theme.of(context).textTheme.bodyMedium), + Row( + children: [ + OverflowBar( + children: [ + SolidButton( + text: " Link to GitHub ", + onPressed: () => _launchGithub(), + ), + SolidButton( + text: " View Resume ", + onPressed: () => _launchResume(), + ), + ], ), - const SizedBox(height: 10), - Text(userData.school, - style: Theme.of(context).textTheme.headline3 - ), - Text(userData.major, - style: Theme.of(context).textTheme.bodyText2 - ), - Text("Expected graduation "+ userData.graduationYear.toString(), - style: Theme.of(context).textTheme.bodyText2 - ), - Row( - children: [ - ButtonBar( - children: [ - SolidButton( - text: " Link to GitHub ", - onPressed: () => _launchGithub(), - ), - SolidButton( - text: " View Resume ", - onPressed: () => _launchResume(), - ), - ], - ), - ], - ) - /*SizedBox(height: 8), - Text("Bio:", - style: Theme + ], + ) + /*SizedBox(height: 8), + Text("Bio:", + style: Theme + .of(context) + .textTheme + .bodyMedium + ), + Container( + height: 100, + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: darken(Theme .of(context) - .textTheme - .bodyText2 - ), - Container( - height: 100, - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - color: darken(Theme + .colorScheme + .surface, 0.04), + borderRadius: BorderRadius + .circular(15), + ), + child: SingleChildScrollView( + child: Text( + "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.", + style: Theme .of(context) - .colorScheme - .surface, 0.04), - borderRadius: BorderRadius - .circular(15), + .textTheme + .bodyMedium, ), - child: SingleChildScrollView( - child: Text( - "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.", - style: Theme - .of(context) - .textTheme - .bodyText2, - ), - ) - ), - SizedBox(height: 8),*/ - ] ) + ), + SizedBox(height: 8),*/ + ]) ], ), - ) - ) - ) - ); + )))); } -} \ No newline at end of file +} diff --git a/lib/pages/project_submission.dart b/lib/pages/project_submission.dart index f668670..392cd09 100644 --- a/lib/pages/project_submission.dart +++ b/lib/pages/project_submission.dart @@ -5,11 +5,13 @@ import 'package:thdapp/components/ErrorDialog.dart'; import 'package:thdapp/components/buttons/GradBox.dart'; import 'package:thdapp/components/buttons/SolidButton.dart'; import 'package:thdapp/components/loading/LoadingOverlay.dart'; +import 'package:thdapp/models/config.dart'; import 'enter_prizes.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/project.dart'; import '../models/team.dart'; import 'team_api.dart'; +import 'table_submission.dart'; class ProjSubmit extends StatefulWidget { @override @@ -21,32 +23,29 @@ class ProjSubmitTextField extends StatelessWidget { final String fieldName; final bool isOptional; - const ProjSubmitTextField(this.controller, this.fieldName, {this.isOptional = false}); + const ProjSubmitTextField(this.controller, this.fieldName, + {this.isOptional = false}); @override Widget build(BuildContext context) { return TextFormField( decoration: InputDecoration( - labelText: fieldName + (isOptional ? " (optional)" : ""), - errorStyle: TextStyle(color: Theme.of(context).colorScheme.error) - ), - style: Theme.of(context).textTheme.bodyText2, + labelText: fieldName + (isOptional ? " (optional)" : ""), + errorStyle: TextStyle(color: Theme.of(context).colorScheme.error)), + style: Theme.of(context).textTheme.bodyMedium, controller: controller, - validator: (String value) { - if (value.isEmpty && !isOptional) { + validator: (value) { + if (value != null && value.isEmpty && !isOptional) { return '$fieldName is required'; } return null; }, ); } - - } class _ProjSubmitState extends State { - - OverlayEntry loading; + OverlayEntry? loading; bool hasProj = false; String _projName = ""; String _projDesc = ""; @@ -59,24 +58,28 @@ class _ProjSubmitState extends State { TextEditingController githubController = TextEditingController(); TextEditingController slidesController = TextEditingController(); TextEditingController videoController = TextEditingController(); + final tableNumberController = TextEditingController(); + ExpoConfig? expoConfig; + bool canSubmitTable = true; final GlobalKey _formKey = GlobalKey(); - String id; - String token; - String projId; + String id = ""; + String token = ""; + String projId = ""; List prizes = []; - Team team; + Team? team; void getData() async { SharedPreferences prefs = await SharedPreferences.getInstance(); - id = prefs.getString('id'); - token = prefs.getString('token'); + id = prefs.getString('id') ?? ""; + token = prefs.getString('token') ?? ""; team = await getUserTeam(token); - Project proj = await getProject(id, token); + Project? proj = await getProject(id, token); + if (proj != null) { hasProj = true; projId = proj.id; @@ -87,7 +90,6 @@ class _ProjSubmitState extends State { _vidUrl = proj.video; isPresenting = proj.presentingVirtually; prizes = proj.prizes; - nameController.text = _projName; descController.text = _projDesc; slidesController.text = _presUrl; @@ -95,30 +97,31 @@ class _ProjSubmitState extends State { githubController.text = _githubUrl; } - if (loading != null) { - loading.remove(); - } + nameController.text = _projName; + descController.text = _projDesc; + slidesController.text = _presUrl; + videoController.text = _vidUrl; + githubController.text = _githubUrl; - setState(() { + loading?.remove(); - }); + setState(() {}); } @override initState() { super.initState(); + _loadExpoConfig(); getData(); WidgetsBinding.instance.addPostFrameCallback((_) { - loading = LoadingOverlay(context); - setState(() { - - }); - Overlay.of(context).insert(loading); + loading = loadingOverlay(context); + setState(() {}); + Overlay.of(context).insert(loading!); }); } @override - void dispose(){ + void dispose() { nameController.dispose(); descController.dispose(); githubController.dispose(); @@ -127,17 +130,57 @@ class _ProjSubmitState extends State { super.dispose(); } - void submitDialog (BuildContext context) { + Future _loadExpoConfig() async { + try { + expoConfig = await getExpoConfig(token); + setState(() { + canSubmitTable = DateTime.now().isBefore(expoConfig!.expoStartTime); + }); + } catch (e) { + print('Failed to load expo config: $e'); + } + } + + void submitDialog(BuildContext context) { Future proj; - if(team == null){ + int? tableNum; + if (tableNumberController.text.isNotEmpty) { + tableNum = int.tryParse(tableNumberController.text); + } + + if (team == null) { errorDialog(context, "Error", "You are not in a team!"); return; - } else if (projId != null){ - proj = editProject(context, nameController.text, descController.text, slidesController.text, videoController.text, githubController.text, isPresenting, projId, token); + } else if (projId != "") { + proj = editProject( + context, + nameController.text, + descController.text, + slidesController.text, + videoController.text, + githubController.text, + isPresenting, + projId, + token, + tableNumber: tableNum, + ); } else { - proj = newProject(context, nameController.text, descController.text, team.teamID, slidesController.text, videoController.text, githubController.text, isPresenting, projId, token); + proj = newProject( + context, + nameController.text, + descController.text, + team?.teamID ?? "", + slidesController.text, + videoController.text, + githubController.text, + isPresenting, + projId, + token, + tableNumber: tableNum, + ); } + showDialog( context: context, builder: (BuildContext context) { @@ -147,17 +190,20 @@ class _ProjSubmitState extends State { if (snapshot.hasData) { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Success", style: Theme.of(context).textTheme.headline1), - content: Text("Project info was saved.", style: Theme.of(context).textTheme.bodyText2), + title: Text("Success", + style: Theme.of(context).textTheme.displayLarge), + content: Text("Project info was saved.", + style: Theme.of(context).textTheme.bodyMedium), actions: [ TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.pop(context); - Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => ProjSubmit())); + Navigator.of(context).pushReplacement(MaterialPageRoute( + builder: (context) => ProjSubmit())); }, ), ], @@ -165,13 +211,15 @@ class _ProjSubmitState extends State { } else if (snapshot.hasError) { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Error", style: Theme.of(context).textTheme.headline1), - content: Text("Project info failed to save. Please try again.", style: Theme.of(context).textTheme.bodyText2), + title: Text("Error", + style: Theme.of(context).textTheme.displayLarge), + content: Text("Project info failed to save. Please try again.", + style: Theme.of(context).textTheme.bodyMedium), actions: [ TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -182,12 +230,12 @@ class _ProjSubmitState extends State { } return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Processing...", style: Theme.of(context).textTheme.headline1), + title: Text("Processing...", + style: Theme.of(context).textTheme.displayLarge), content: Container( alignment: Alignment.center, height: 70, - child: const CircularProgressIndicator() - ), + child: const CircularProgressIndicator()), ); }, ); @@ -195,6 +243,30 @@ class _ProjSubmitState extends State { ); } + Widget _buildTableNumberField() { + if (!canSubmitTable) { + return const SizedBox.shrink(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ProjSubmitTextField( + tableNumberController, + "Table Number", + isOptional: true, + ), + Text( + "Table number can only be submitted before expo starts", + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + const SizedBox(height: 8), + ], + ); + } + @override Widget build(BuildContext context) { final mqData = MediaQuery.of(context); @@ -202,67 +274,98 @@ class _ProjSubmitState extends State { final screenWidth = mqData.size.width; return DefaultPage( - reverse: true, - child: - Container( + reverse: true, + child: Container( alignment: Alignment.topCenter, padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), child: GradBox( - alignment: Alignment.topCenter, - width: screenWidth*0.9, - height: screenHeight*0.75, - padding: const EdgeInsets.fromLTRB(20, 15, 20, 15), - child: Form( - key: _formKey, - child: SingleChildScrollView( - child: Column( + alignment: Alignment.topCenter, + width: screenWidth * 0.9, + height: screenHeight * 0.75, + padding: const EdgeInsets.fromLTRB(20, 15, 20, 15), + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("PROJECT SUBMISSION", style: Theme.of(context).textTheme.headline2), - const SizedBox(height:16), + Text("PROJECT SUBMISSION", + style: Theme.of(context).textTheme.displayMedium), + const SizedBox(height: 16), ProjSubmitTextField(nameController, "Project Name"), - const SizedBox(height:8), - ProjSubmitTextField(descController, "Project Description"), - const SizedBox(height:8), - ProjSubmitTextField(githubController, "GitHub Repository URL"), - const SizedBox(height:8), - ProjSubmitTextField(slidesController, "Presentation Slides URL"), - const SizedBox(height:8), - ProjSubmitTextField(videoController, "Video URL", isOptional: true), - const SizedBox(height:8), + const SizedBox(height: 8), + ProjSubmitTextField( + descController, "Project Description"), + const SizedBox(height: 8), + ProjSubmitTextField( + githubController, "GitHub Repository URL"), + const SizedBox(height: 8), + ProjSubmitTextField( + slidesController, "Presentation Slides URL"), + const SizedBox(height: 8), + ProjSubmitTextField(videoController, "Video URL", + isOptional: true), + const SizedBox(height: 8), + _buildTableNumberField(), + const SizedBox(height: 8), SolidButton( - text: "Save", - onPressed: () { - if (!_formKey.currentState.validate()) { - return; - } + text: "Save", + onPressed: () { + if (!_formKey.currentState!.validate()) { + return; + } - _formKey.currentState.save(); + _formKey.currentState?.save(); - submitDialog(context); - }, + submitDialog(context); + }, ), SolidButton( - text: "Submit for Prizes", + text: "Submit for Prizes", + onPressed: () { + if (hasProj) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EnterPrizes( + projId: projId, + enteredPrizes: prizes, + )), + ); + } else { + errorDialog(context, "Error", + "You do not have a project to enter!"); + } + }, + ), + if (hasProj) + SolidButton( + text: "Submit Table Number", onPressed: () { - if (hasProj) { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => - EnterPrizes(projId: projId, enteredPrizes: prizes,)), - ); - } else { - errorDialog(context, "Error", "You do not have a project to enter!"); - } + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TableSubmission( + project: Project( + id: projId, + name: nameController.text, + desc: descController.text, + event: "", + url: githubController.text, + slides: slidesController.text, + video: videoController.text, + team: team?.teamID ?? "", + prizes: prizes, + presentingVirtually: isPresenting, + tableNumber: null, + ), + ), + ), + ); }, - ) + ), ], - ) - ) - ) - ) - ) - ); + )))))); } -} \ No newline at end of file +} diff --git a/lib/pages/qr_scan_config.dart b/lib/pages/qr_scan_config.dart index ed5c807..d69e180 100644 --- a/lib/pages/qr_scan_config.dart +++ b/lib/pages/qr_scan_config.dart @@ -11,27 +11,21 @@ class ScanConfigPage extends StatelessWidget { final screenWidth = mqData.size.width; return DefaultPage( - backflag: true, - reverse: true, - child: - Container( - alignment: Alignment.center, - height: screenHeight * 0.78, - padding: const EdgeInsets.fromLTRB(15, 20, 15, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ScanConfigBox() - ], - ) - ) - ); + backflag: true, + reverse: true, + child: Container( + alignment: Alignment.center, + height: screenHeight * 0.78, + padding: const EdgeInsets.fromLTRB(15, 20, 15, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ScanConfigBox()], + ))); } } class ScanConfigBox extends StatelessWidget { - @override Widget build(BuildContext context) { return GradBox( @@ -44,65 +38,79 @@ class ScanConfigBox extends StatelessWidget { children: [ Text( "SCAN CONFIG", - style: Theme.of(context).textTheme.headline2, + style: Theme.of(context).textTheme.displayMedium, textAlign: TextAlign.left, ), - const SizedBox(height: 40,), + const SizedBox( + height: 40, + ), Text( "Check In Item", - style: Theme.of(context).textTheme.headline3, + style: Theme.of(context).textTheme.displaySmall, + ), + const SizedBox( + height: 15, + ), + DropdownButtonFormField( + items: [ + DropdownMenuItem( + child: Text( + "Ctrl+F - Working with your team", + style: Theme.of(context).textTheme.bodyMedium, + ), + ) + ], + onChanged: (value) {}, + ), + const SizedBox( + height: 25, ), - const SizedBox(height: 15,), - DropdownButtonFormField(items: [DropdownMenuItem( - child: Text("Ctrl+F - Working with your team", - style: Theme.of(context).textTheme.bodyText2, - ), - )], onChanged: (value) { },), - const SizedBox(height: 25,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "View History", - style: Theme.of(context).textTheme.headline3, + style: Theme.of(context).textTheme.displaySmall, ), Transform.scale( scale: 1.5, child: Checkbox( side: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 2), + color: Theme.of(context).colorScheme.primary, width: 2), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), ), value: false, - onChanged: (val){}), + onChanged: (val) {}), ) ], ), - const SizedBox(height: 25,), + const SizedBox( + height: 25, + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Self-checkin", - style: Theme.of(context).textTheme.headline3, + style: Theme.of(context).textTheme.displaySmall, ), Transform.scale( scale: 1.5, child: Checkbox( side: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 2), + color: Theme.of(context).colorScheme.primary, width: 2), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), ), value: false, - onChanged: (val){}), + onChanged: (val) {}), ) ], ), - const SizedBox(height: 40,), + const SizedBox( + height: 40, + ), Align( alignment: Alignment.center, child: SizedBox( @@ -110,7 +118,7 @@ class ScanConfigBox extends StatelessWidget { width: 160, child: SolidButton( text: "Confirm", - onPressed: (){}, + onPressed: () {}, ), ), ) @@ -119,4 +127,3 @@ class ScanConfigBox extends StatelessWidget { ); } } - diff --git a/lib/pages/see_invites.dart b/lib/pages/see_invites.dart index c48cfa0..bd63254 100644 --- a/lib/pages/see_invites.dart +++ b/lib/pages/see_invites.dart @@ -10,22 +10,27 @@ import '/models/team.dart'; import 'view_team.dart'; class AcceptButtonRow extends StatelessWidget { - final Function acceptOnPressed; - final Function declineOnPressed; + final void Function() acceptOnPressed; + final void Function() declineOnPressed; - const AcceptButtonRow({this.acceptOnPressed, this.declineOnPressed}); + const AcceptButtonRow( + {required this.acceptOnPressed, required this.declineOnPressed}); @override Widget build(BuildContext context) { - return Row ( + return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - const SizedBox(width: 20,), + const SizedBox( + width: 20, + ), SolidButton( text: "Accept", onPressed: acceptOnPressed, ), - const SizedBox(width: 20,), + const SizedBox( + width: 20, + ), SolidButton( text: "Decline", onPressed: declineOnPressed, @@ -36,13 +41,13 @@ class AcceptButtonRow extends StatelessWidget { } class NoAcceptButtonRow extends StatelessWidget { - final Function cancelOnPressed; + final void Function()? cancelOnPressed; - const NoAcceptButtonRow({this.cancelOnPressed}); + const NoAcceptButtonRow({required this.cancelOnPressed}); @override Widget build(BuildContext context) { - return Row ( + return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ SolidButton( @@ -59,98 +64,113 @@ class RequestCard extends StatelessWidget { final Function(dynamic) removeRequest; const RequestCard(this.request, this.removeRequest); - - bool checkAdmin(BuildContext context) { + + bool? checkAdmin(BuildContext context) { bool hasTeam = Provider.of(context, listen: false).hasTeam; if (!hasTeam) return false; - Team team = Provider.of(context, listen: false).team; - List adminIds = team.admins.map((mem) => mem.id).toList(); + Team? team = Provider.of(context, listen: false).team; + List? adminIds = team?.admins.map((mem) => mem.id).toList(); String id = Provider.of(context, listen: false).id; - return adminIds.contains(id); + return adminIds?.contains(id); } @override Widget build(BuildContext context) { String requestType = request['type']; bool hasTeam = Provider.of(context, listen: false).hasTeam; - bool canAccept = (!hasTeam && requestType == "INVITE") - || (hasTeam && requestType == "JOIN" && checkAdmin(context)); + bool? checkAdminResult = checkAdmin(context); + bool canAccept = (!hasTeam && requestType == "INVITE") || + (hasTeam && requestType == "JOIN" && checkAdminResult!=null && checkAdminResult); - - String inviteInfo = hasTeam ? request['user']['email'] : request['team']['name']; + String inviteInfo = + hasTeam ? request['user']['email'] : request['team']['name']; String requestID = request['_id']; String token = Provider.of(context, listen: false).token; return Card( margin: const EdgeInsets.all(12), - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(requestType, style: Theme.of(context).textTheme.headline3), + Text(requestType, + style: Theme.of(context).textTheme.displaySmall), const SizedBox(height: 10), - Text(inviteInfo, style: Theme.of(context).textTheme.bodyText2), + Text(inviteInfo, style: Theme.of(context).textTheme.bodyMedium), const SizedBox(height: 8), - canAccept ? AcceptButtonRow(acceptOnPressed: () async { - bool success = await acceptRequest(token, requestID); - if (!success) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Error accepting invite. " - "Please request for the invite to be resent."), - )); - removeRequest(request); - } - await Provider.of(context, listen: false).fetchUserInfo(); - if (requestType == 'INVITE') { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (ctx) => ViewTeam(), - settings: const RouteSettings( - arguments: "", - ) - ), (route) => route.isFirst); - } - removeRequest(request); - }, declineOnPressed: () async { - await declineRequest(token, requestID); - removeRequest(request); - }) : NoAcceptButtonRow(cancelOnPressed: () async { - await cancelRequest(token, requestID); - removeRequest(request); - }) + canAccept + ? AcceptButtonRow(acceptOnPressed: () async { + bool success = await acceptRequest(token, requestID); + if (!success) { + ScaffoldMessenger.of(context) + .showSnackBar(const SnackBar( + content: Text("Error accepting invite. " + "Please request for the invite to be resent."), + )); + removeRequest(request); + } + await Provider.of(context, listen: false) + .fetchUserInfo(); + if (requestType == 'INVITE') { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (ctx) => ViewTeam(), + settings: const RouteSettings( + arguments: "", + )), + (route) => route.isFirst); + } + removeRequest(request); + }, declineOnPressed: () async { + await declineRequest(token, requestID); + removeRequest(request); + }) + : NoAcceptButtonRow(cancelOnPressed: () async { + await cancelRequest(token, requestID); + removeRequest(request); + }) ], - ) - ) - ); + ))); } } - class ViewInvites extends StatefulWidget { @override _ViewInvitesState createState() => _ViewInvitesState(); } class _ViewInvitesState extends State { - List requestsList; + List requestsList = []; Status fetchStatus = Status.notLoaded; Future fetchData() async { String token = Provider.of(context, listen: false).token; bool hasTeam = Provider.of(context, listen: false).hasTeam; - List fetchedList; + List? fetchedList; if (hasTeam) { fetchedList = await getTeamMail(token); } else { - fetchedList = await getUserMail(token); + try { + fetchedList = await getUserMail(token); + } on String catch (e) { + if (e.toString().contains("You're already in a team!")) { + await Provider.of(context, listen: false) + .fetchUserInfo(); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => ViewTeam()), + (route) => route.isFirst); + } + } + } setState(() { fetchStatus = fetchedList == null ? Status.error : Status.loaded; - requestsList = fetchedList; + requestsList = fetchedList ?? []; }); } @@ -172,52 +192,58 @@ class _ViewInvitesState extends State { final screenHeight = mqData.size.height; final screenWidth = mqData.size.width; return DefaultPage( - backflag: true, - reverse: true, - child: - Container( - alignment: Alignment.center, - padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), - child: RefreshIndicator( - onRefresh: fetchData, - child: GradBox( - width: screenWidth*0.9, - height: screenHeight*0.75, - padding: const EdgeInsets.fromLTRB(20, 5, 20, 5), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.fromLTRB(0, 10, 0, 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("INVITES", style: Theme.of(context).textTheme.headline1) - ] - ) - ), - if (fetchStatus == Status.loaded && requestsList.isNotEmpty) - Expanded( - child: ListView.builder( - itemCount: requestsList.length, - itemBuilder: (context, index) { - return RequestCard(requestsList[index], removeRequest); - }, - ), - ) - else if (fetchStatus == Status.loaded && requestsList.isEmpty) - ListRefreshable(child: Text("No invites", style: Theme.of(context).textTheme.bodyText2,)) - else if (fetchStatus == Status.error) - ListRefreshable(child: Text("Error loading invites", style: Theme.of(context).textTheme.bodyText2,)) - else - const Center(child: CircularProgressIndicator()) - ] - ) - ), - ) - ) - ); + backflag: true, + reverse: true, + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), + child: RefreshIndicator( + onRefresh: fetchData, + child: GradBox( + width: screenWidth * 0.9, + height: screenHeight * 0.75, + padding: const EdgeInsets.fromLTRB(20, 5, 20, 5), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.fromLTRB(0, 10, 0, 10), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text("INVITES", + style: + Theme.of(context).textTheme.displayLarge) + ])), + if (fetchStatus == Status.loaded && + requestsList.isNotEmpty) + Expanded( + child: ListView.builder( + itemCount: requestsList.length, + itemBuilder: (context, index) { + return RequestCard( + requestsList[index], removeRequest); + }, + ), + ) + else if (fetchStatus == Status.loaded && + requestsList.isEmpty) + ListRefreshable( + child: Text( + "No invites", + style: Theme.of(context).textTheme.bodyMedium, + )) + else if (fetchStatus == Status.error) + ListRefreshable( + child: Text( + "Error loading invites", + style: Theme.of(context).textTheme.bodyMedium, + )) + else + const Center(child: CircularProgressIndicator()) + ])), + ))); } } - diff --git a/lib/pages/sponsors.dart b/lib/pages/sponsors.dart index 51130e3..a1b548b 100644 --- a/lib/pages/sponsors.dart +++ b/lib/pages/sponsors.dart @@ -4,7 +4,6 @@ import 'package:thdapp/components/ErrorDialog.dart'; import 'package:thdapp/components/buttons/GradBox.dart'; import 'package:thdapp/components/buttons/SolidButton.dart'; import 'package:thdapp/pages/bookmarks.dart'; -import '../models/profile.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:thdapp/api.dart'; import 'package:flutter_smart_scan/flutter_smart_scan.dart'; @@ -15,30 +14,29 @@ import 'package:flutter/services.dart'; class Sponsors extends StatefulWidget { @override - _SponsorsState createState() => _SponsorsState(); + SponsorsState createState() => SponsorsState(); } -class _SponsorsState extends State { - String myName; - List studentIds; - List students; // bunch of Profiles - List studentTeams; - Map bookmarks; // dictionary of participant id : actual bookmark id - DiscordInfo discordInfo; +class SponsorsState extends State { + String myName = ""; + List studentIds = []; + List students = []; // bunch of Profiles + List studentTeams = []; + Map bookmarks = {}; // dictionary of participant id : actual bookmark id + DiscordInfo discordInfo = DiscordInfo(code: "", expiry: "", link: ""); - int searchResultCount; - bool searchPressed; + int searchResultCount = 0; + bool searchPressed = false; String placeholderText = "Search for participants by name."; - SharedPreferences prefs; - String token; + String token = ""; final myController = TextEditingController(); void getData() async { - prefs = await SharedPreferences.getInstance(); - token = prefs.getString('token'); + SharedPreferences prefs = await SharedPreferences.getInstance(); + token = prefs.getString('token') ?? ""; bookmarks = await getBookmarkIdsList(token); discordInfo = await getDiscordInfo(token); setState(() {}); @@ -46,8 +44,8 @@ class _SponsorsState extends State { _launchDiscord() async { String url = discordInfo.link; - if (await canLaunch(url)) { - await launch(url); + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url)); } else { errorDialog(context, "Error", 'Could not launch Discord Server.'); } @@ -68,19 +66,21 @@ class _SponsorsState extends State { future: discordCode, builder: (context, snapshot) { if (snapshot.hasData) { + DiscordInfo discordInfo = snapshot.data as DiscordInfo; + return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: Text("Verification Code", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( "When you join our Discord server, you'll be prompted to enter the following verification code by the Discord Bot running the server. This code will expire in 10 minutes.\n", - style: Theme.of(context).textTheme.bodyText2), + style: Theme.of(context).textTheme.bodyMedium), Text( - snapshot.data.code, - style: Theme.of(context).textTheme.headline3, + discordInfo.code, + style: Theme.of(context).textTheme.displaySmall, textAlign: TextAlign.center, ) ], @@ -89,17 +89,16 @@ class _SponsorsState extends State { TextButton( child: Text( "COPY", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { - Clipboard.setData( - ClipboardData(text: snapshot.data.code)); + Clipboard.setData(ClipboardData(text: discordInfo.code)); }, ), TextButton( child: Text( "GO TO SERVER", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { _launchDiscord(); @@ -111,15 +110,15 @@ class _SponsorsState extends State { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: Text("Error", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), content: Text( "We ran into an error while getting your Discord verification code", - style: Theme.of(context).textTheme.bodyText2), + style: Theme.of(context).textTheme.bodyMedium), actions: [ TextButton( child: Text( "OK", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -131,7 +130,7 @@ class _SponsorsState extends State { return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, title: Text("Verifying...", - style: Theme.of(context).textTheme.headline1), + style: Theme.of(context).textTheme.displayLarge), content: Container( alignment: Alignment.center, height: 70, @@ -147,7 +146,7 @@ class _SponsorsState extends State { studentIds = []; students = []; studentTeams = []; - placeholderText = null; + placeholderText = ""; setState(() {}); String query = myController.text == "" ? '\u00A0' : myController.text; @@ -164,7 +163,7 @@ class _SponsorsState extends State { int counter = 0; for (var i = 0; i < students.length; i++) { if (students[i] != null) { - var name = students[i].firstName + students[i].lastName; + var name = "${students[i].firstName} ${students[i].lastName}"; if (name.contains(keyword)) { counter++; } @@ -176,7 +175,7 @@ class _SponsorsState extends State { void newBookmark(String bookmarkId, String participantId) async { prefs = await SharedPreferences.getInstance(); - token = prefs.getString('token'); + token = prefs.getString('token') ?? ""; String newBookmarkId = await addBookmark(token, participantId); bookmarks[newBookmarkId] = participantId; setState(() {}); @@ -184,7 +183,7 @@ class _SponsorsState extends State { void removeBookmark(String bookmarkId, String participantId) async { prefs = await SharedPreferences.getInstance(); - token = prefs.getString('token'); + token = prefs.getString('token') ?? ""; bookmarks.remove(bookmarkId); deleteBookmark(token, bookmarkId); setState(() {}); @@ -209,225 +208,205 @@ class _SponsorsState extends State { final screenWidth = mqData.size.width; return DefaultPage( - isSponsor: true, - reverse: true, - child: - Container( - padding: EdgeInsets.fromLTRB( - screenWidth * 0.08, 0, screenWidth * 0.08, 0), - height: screenHeight * 0.8, - child: Column(children: [ - Container( - alignment: Alignment.topLeft, - //padding: EdgeInsets.fromLTRB(35, 0, 10, 0), + isSponsor: true, + reverse: true, + child: Container( + padding: EdgeInsets.fromLTRB( + screenWidth * 0.08, 0, screenWidth * 0.08, 0), + height: screenHeight * 0.8, + child: Column(children: [ + Container( + alignment: Alignment.topLeft, + //padding: EdgeInsets.fromLTRB(35, 0, 10, 0), + + child: Text("Welcome to TartanHacks!", + style: Theme.of(context).textTheme.displayLarge), + ), + const SizedBox(height: 10), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SolidButton( + color: Theme.of(context).colorScheme.tertiaryContainer, + child: Text( + " Scan ", + style: Theme.of(context) + .textTheme + .displayMedium + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onTertiaryContainer), + ), + onPressed: () async { + String id = await FlutterBarcodeScanner.scanBarcode( + '#ff6666', 'Cancel', true, ScanMode.QR); + if (["-1", "", null].contains(id)) return; + + await getProfile(id, token); - child: Text("Welcome to TartanHacks!", - style: Theme.of(context) - .textTheme - .headline1), - ), - const SizedBox(height: 10), - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SolidButton( - color: Theme.of(context) - .colorScheme - .tertiaryContainer, - child: Text( - " Scan ", - style: Theme.of(context) - .textTheme - .headline2 - .copyWith( - color: Theme.of(context) - .colorScheme - .onTertiaryContainer), - ), - onPressed: () async { - String id = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Cancel', true, ScanMode.QR); - if (["-1", "", null].contains(id)) return; - Profile isValid = - await getProfile(id, token); - if (isValid != null) { Navigator.push( context, MaterialPageRoute( - builder: (context) => - ProfilePage( + builder: (context) => ProfilePage( bookmarks: bookmarks, ), settings: RouteSettings( arguments: id, )), ).then((value) => getBookmarks()); - } else { - errorDialog(context, "Error", - "Invalid user ID."); - } - }, - ), - SolidButton( - color: Theme.of(context) - .colorScheme - .tertiaryContainer, - child: Text( - " Bookmarks ", - style: Theme.of(context) - .textTheme - .headline2 - .copyWith( - color: Theme.of(context) - .colorScheme - .onTertiaryContainer), + }, ), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - Bookmarks()), - ); - }, - ) - ], - ), - SolidButton( - color: Theme.of(context) - .colorScheme - .tertiaryContainer, - child: Text( - " Discord Server ", - style: Theme.of(context) - .textTheme - .headline2 - .copyWith( - color: Theme.of(context) - .colorScheme - .onTertiaryContainer), + SolidButton( + color: Theme.of(context).colorScheme.tertiaryContainer, + child: Text( + " Bookmarks ", + style: Theme.of(context) + .textTheme + .displayMedium + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onTertiaryContainer), + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Bookmarks()), + ); + }, + ) + ], ), - onPressed: () { - discordVerifyDialog(context); - }, - ), - ], - ), - const SizedBox(height: 10), - Container( - alignment: Alignment.centerLeft, - child: Text("Search", - textAlign: TextAlign.left, - style: Theme.of(context).textTheme.headline3 - .copyWith(color: Theme.of(context).colorScheme.onPrimary))), - Row(children: [ - Expanded( - child: TextField( - style: Theme.of(context).textTheme.bodyText2 - .copyWith(color: Theme.of(context).colorScheme.onPrimary), - decoration: InputDecoration( - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(width: 2.0, color: Theme.of(context).colorScheme.onPrimary), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(width: 2.0, color: Theme.of(context).colorScheme.onPrimary), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) - ), - disabledBorder: OutlineInputBorder( - borderSide: BorderSide(width: 2.0, color: Theme.of(context).colorScheme.onPrimary.withAlpha(87)), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) + SolidButton( + color: Theme.of(context).colorScheme.tertiaryContainer, + child: Text( + " Discord Server ", + style: Theme.of(context) + .textTheme + .displayMedium + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onTertiaryContainer), ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(width: 2.0, color: Theme.of(context).colorScheme.onPrimary), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) + onPressed: () { + discordVerifyDialog(context); + }, + ), + ], + ), + const SizedBox(height: 10), + Container( + alignment: Alignment.centerLeft, + child: Text("Search", + textAlign: TextAlign.left, + style: Theme.of(context).textTheme.displaySmall?.copyWith( + color: Theme.of(context).colorScheme.onPrimary))), + Row(children: [ + Expanded( + child: TextField( + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onPrimary), + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 2.0, + color: Theme.of(context).colorScheme.onPrimary), + borderRadius: + const BorderRadius.all(Radius.circular(15.0))), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 2.0, + color: Theme.of(context).colorScheme.onPrimary), + borderRadius: + const BorderRadius.all(Radius.circular(15.0))), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 2.0, + color: Theme.of(context) + .colorScheme + .onPrimary + .withAlpha(87)), + borderRadius: + const BorderRadius.all(Radius.circular(15.0))), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 2.0, + color: Theme.of(context).colorScheme.onPrimary), + borderRadius: + const BorderRadius.all(Radius.circular(15.0))), ), + enableSuggestions: false, + controller: myController, + textInputAction: TextInputAction.send, + onSubmitted: (value) { + search(); + }, ), - enableSuggestions: false, - controller: myController, - textInputAction: TextInputAction.send, - onSubmitted: (value) { - search(); - }, ), - ), - const SizedBox( - width: 10, - ), - SolidButton( - onPressed: (){ - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.unfocus(); - } - search(); - }, - child: Icon(Icons.subdirectory_arrow_left, - size: 30, - color: Theme.of(context) - .colorScheme - .onPrimary)), - ]), - const SizedBox(height: 25), - if (students != null && students.isNotEmpty) - Expanded( - child: Container( - alignment: Alignment.bottomCenter, - child: ListView.builder( - itemCount: students.length, - itemBuilder: - (BuildContext context, - int index) { - bool isBookmark = (bookmarks.isNotEmpty) - ? bookmarks.containsValue( - studentIds[index]) - : false; - return InfoTile( - name: (students[index] != null) - ? students[index].firstName + - " " + - students[index] - .lastName - : "NULL", - team: (studentTeams[index] != null) - ? studentTeams[index] - : "No team", - bio: (students[index] != null) - ? students[index].college + - " c/o " + - students[index] - .graduationYear - .toString() - : "NULL", - participantId: - studentIds[index], - bookmarkId: - (bookmarks.isNotEmpty && bookmarks.containsValue(studentIds[index])) - ? bookmarks.keys - .firstWhere((k) => - bookmarks[k] == - studentIds[ - index]) - : null, - isBookmark: isBookmark, - toggleFn: isBookmark - ? removeBookmark - : newBookmark, - bmMap: bookmarks, - updateBM: getBookmarks); - }))) - else - Center(child: - placeholderText != null - ? Text(placeholderText, style: TextStyle(color: Theme.of(context).colorScheme.onPrimary),) - : const CircularProgressIndicator() - ) - ]) - ) - ); + const SizedBox( + width: 10, + ), + SolidButton( + onPressed: () { + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.unfocus(); + } + search(); + }, + child: Icon(Icons.subdirectory_arrow_left, + size: 30, + color: Theme.of(context).colorScheme.onPrimary)), + ]), + const SizedBox(height: 25), + if (students.isNotEmpty) + Expanded( + child: Container( + alignment: Alignment.bottomCenter, + child: ListView.builder( + itemCount: students.length, + itemBuilder: (BuildContext context, int index) { + bool isBookmark = (bookmarks.isNotEmpty) + ? bookmarks.containsValue(studentIds[index]) + : false; + return InfoTile( + name: (students[index] != null) + ? "${students[index].firstName} ${students[index].lastName}" + : "NULL", + team: (studentTeams[index] != null) + ? studentTeams[index] + : "No team", + bio: (students[index] != null) + ? "${students[index].college} c/o ${students[index].graduationYear}" + : "NULL", + participantId: studentIds[index], + bookmarkId: (bookmarks.isNotEmpty && + bookmarks + .containsValue(studentIds[index])) + ? bookmarks.keys.firstWhere((k) => + bookmarks[k] == studentIds[index]) + : null, + isBookmark: isBookmark, + toggleFn: + isBookmark ? removeBookmark : newBookmark, + bmMap: bookmarks, + updateBM: getBookmarks); + }))) + else + Center( + child: Text( + placeholderText, + style: + TextStyle(color: Theme.of(context).colorScheme.onPrimary), + )) + ]))); } } @@ -444,15 +423,15 @@ class InfoTile extends StatelessWidget { final Function updateBM; const InfoTile( - {this.name, - this.team, - this.bio, - this.participantId, - this.bookmarkId, - this.isBookmark, - this.toggleFn, - this.bmMap, - this.updateBM}); + {required this.name, + required this.team, + required this.bio, + required this.participantId, + required this.bookmarkId, + required this.isBookmark, + required this.toggleFn, + required this.bmMap, + required this.updateBM}); @override Widget build(BuildContext context) { @@ -477,13 +456,13 @@ class InfoTile extends StatelessWidget { }, elevation: 2.0, fillColor: Theme.of(context).colorScheme.primary, + padding: const EdgeInsets.all(12), + shape: const CircleBorder(), child: Icon( Icons.person, size: 30.0, color: Theme.of(context).colorScheme.onPrimary, ), - padding: const EdgeInsets.all(12), - shape: const CircleBorder(), ), SizedBox( width: 180, @@ -494,17 +473,17 @@ class InfoTile extends StatelessWidget { Text( name, textAlign: TextAlign.left, - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), Text( team, textAlign: TextAlign.left, - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ), Text( bio, textAlign: TextAlign.left, - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, ), ]), ), diff --git a/lib/pages/table_submission.dart b/lib/pages/table_submission.dart new file mode 100644 index 0000000..9a7bed5 --- /dev/null +++ b/lib/pages/table_submission.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:thdapp/api.dart'; +import 'package:thdapp/components/DefaultPage.dart'; +import 'package:thdapp/components/ErrorDialog.dart'; +import 'package:thdapp/components/buttons/GradBox.dart'; +import 'package:thdapp/components/buttons/SolidButton.dart'; +import 'package:thdapp/providers/expo_config_provider.dart'; +import 'package:thdapp/models/project.dart'; +import 'package:thdapp/providers/user_info_provider.dart'; + +class TableSubmission extends StatefulWidget { + final Project project; + + const TableSubmission({Key? key, required this.project}) : super(key: key); + + @override + _TableSubmissionState createState() => _TableSubmissionState(); +} + +class _TableSubmissionState extends State { + final _formKey = GlobalKey(); + final _tableNumberController = TextEditingController(); + bool _isSubmitting = false; + + @override + void initState() { + super.initState(); + _tableNumberController.text = widget.project.tableNumber?.toString() ?? ''; + } + + @override + void dispose() { + _tableNumberController.dispose(); + super.dispose(); + } + + Future _submitTableNumber() async { + if (!_formKey.currentState!.validate()) return; + + setState(() => _isSubmitting = true); + + try { + final tableNum = int.parse(_tableNumberController.text); + final token = Provider.of(context, listen: false).token; + + final success = await updateProjectTableNumber( + widget.project.id, + tableNum, + token, + ); + + if (success) { + Navigator.pop(context, true); + } else { + errorDialog(context, "Error", "Failed to update table number"); + } + } catch (e) { + errorDialog(context, "Error", "Invalid table number"); + } finally { + setState(() => _isSubmitting = false); + } + } + + @override + Widget build(BuildContext context) { + final expoConfig = Provider.of(context); + final canSubmit = expoConfig.canSubmitTableNumber(); + + return DefaultPage( + backflag: true, + child: Container( + padding: const EdgeInsets.all(16), + child: GradBox( + child: Padding( + padding: const EdgeInsets.all(16), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Submit Table Number", + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 16), + if (!canSubmit) + Container( + padding: const EdgeInsets.all(8), + color: Colors.orange.withAlpha(50), + child: Text( + "Table number submission is closed - expo has started", + style: Theme.of(context).textTheme.bodyMedium, + ), + ), + const SizedBox(height: 16), + TextFormField( + controller: _tableNumberController, + decoration: const InputDecoration( + labelText: "Table Number", + hintText: "Enter your table number", + ), + keyboardType: TextInputType.number, + enabled: canSubmit, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a table number'; + } + if (int.tryParse(value) == null) { + return 'Please enter a valid number'; + } + return null; + }, + ), + const SizedBox(height: 24), + Center( + child: SolidButton( + text: _isSubmitting ? "Submitting..." : "Submit", + onPressed: canSubmit && !_isSubmitting ? _submitTableNumber : null, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/team_api.dart b/lib/pages/team_api.dart index 47ed702..999b466 100644 --- a/lib/pages/team_api.dart +++ b/lib/pages/team_api.dart @@ -3,47 +3,35 @@ import 'dart:async'; import 'dart:convert'; import '/models/team.dart'; -const baseUrl = "https://dev.backend.tartanhacks.com/"; +const baseUrl = "https://backend.tartanhacks.com/"; Future createTeam( String teamName, String description, bool visibility, String token) async { - String url = baseUrl + "team/"; + Uri url = Uri.parse("${baseUrl}team/"); Map headers = { "Content-type": "application/json", "x-access-token": token }; - String body = '{"name": "' + - teamName + - '","description": "' + - description + - '","visible": ' + - visibility.toString() + - '}'; + String body = '{"name": "$teamName","description": "$description","visible": $visibility}'; final response = await http.post(url, headers: headers, body: body); //patch? return response; } Future editTeam( String teamName, String description, bool visibility, String token) async { - String url = baseUrl + "team/"; + Uri url = Uri.parse("${baseUrl}team/"); Map headers = { "Content-type": "application/json", "x-access-token": token }; - String body = '{"name": "' + - teamName + - '","description": "' + - description + - '","visible": ' + - visibility.toString() + - '}'; + String body = '{"name": "$teamName","description": "$description","visible": $visibility}'; final response = await http.patch(url, headers: headers, body: body); //patch? return response; } Future promoteToAdmin(String userID, String token) async { - String url = - baseUrl + "team/promote/" + userID; + Uri url = + Uri.parse("${baseUrl}team/promote/$userID"); Map headers = { "Content-type": "application/json", "x-access-token": token @@ -56,14 +44,14 @@ Future promoteToAdmin(String userID, String token) async { } } -Future getUserTeam(String token) async { - String url = baseUrl + "user/team"; +Future getUserTeam(String token) async { + String url = "${baseUrl}user/team"; Map headers = { "Content-type": "application/json", "x-access-token": token }; - final response = await http.get(url, headers: headers); + final response = await http.get(Uri.parse(url), headers: headers); if (response.statusCode == 200) { var parsedJson = jsonDecode(response.body); if (response.body.contains("You are not in a team!")) { @@ -76,96 +64,96 @@ Future getUserTeam(String token) async { } Future> getTeamMail(String token) async { - String url = baseUrl + "requests/team"; + String url = "${baseUrl}requests/team"; Map headers = { "Content-type": "application/json", "x-access-token": token }; - final response = await http.get(url, headers: headers); + final response = await http.get(Uri.parse(url), headers: headers); if (response.statusCode == 200) { var data = json.decode(response.body); return data; } - return null; + throw Error(); } Future acceptRequest(String token, String requestID) async { String url = - baseUrl + "requests/accept/" + requestID; + "${baseUrl}requests/accept/$requestID"; Map headers = { "Content-type": "application/json", "x-access-token": token }; - http.Response response = await http.post(url, headers: headers); + http.Response response = await http.post(Uri.parse(url), headers: headers); return response.statusCode == 200; } Future cancelRequest(String token, String requestID) async { String url = - baseUrl + "requests/cancel/" + requestID; + "${baseUrl}requests/cancel/$requestID"; Map headers = { "Content-type": "application/json", "x-access-token": token }; - await http.post(url, headers: headers); + await http.post(Uri.parse(url), headers: headers); } Future declineRequest(String token, String requestID) async { String url = - baseUrl + "requests/cancel/" + requestID; + "${baseUrl}requests/decline/$requestID"; Map headers = { "Content-type": "application/json", "x-access-token": token }; print(url); print(headers.toString()); - http.Response response = await http.post(url, headers: headers); + http.Response response = await http.post(Uri.parse(url), headers: headers); print(response.body); } Future> getUserMail(String token) async { - String url = baseUrl + "requests/user"; + String url = "${baseUrl}requests/user"; Map headers = { "Content-type": "application/json", "x-access-token": token }; - final response = await http.get(url, headers: headers); + final response = await http.get(Uri.parse(url), headers: headers); if (response.statusCode == 200) { var data = json.decode(response.body); return data; } - return null; + throw response.body; } Future inviteTeamMember(String userEmail, String token) async { - const url = baseUrl + "team/invite"; + const url = "${baseUrl}team/invite"; Map headers = { "Content-type": "application/json", "x-access-token": token }; var body = json.encode({'email': userEmail}); - await http.post(url, headers: headers, body: body); + await http.post(Uri.parse(url), headers: headers, body: body); } Future leaveTeam(String token) async { - const url = baseUrl + "team/leave"; + const url = "${baseUrl}team/leave"; Map headers = { "Content-type": "application/json", "x-access-token": token }; var body = json.encode({}); - final response = await http.post(url, headers: headers, body: body); + final response = await http.post(Uri.parse(url), headers: headers, body: body); return response.statusCode == 200; } Future requestTeam(String teamID, String token) async { - String url = baseUrl + "team/join/" + teamID; + String url = "${baseUrl}team/join/$teamID"; Map headers = { "Content-type": "application/json", "x-access-token": token }; var body = json.encode({}); - final response = await http.post(url, headers: headers, body: body); + final response = await http.post(Uri.parse(url), headers: headers, body: body); if (response.statusCode == 200) { return true; } @@ -173,13 +161,13 @@ Future requestTeam(String teamID, String token) async { } Future requestTeamMember(String email, String token) async { - String url = baseUrl + "team/invite"; + String url = "${baseUrl}team/invite"; Map headers = { "Content-type": "application/json", "x-access-token": token }; - String body = '{"email":"' + email + '"}'; - final response = await http.post(url, headers: headers, body: body); + String body = '{"email":"$email"}'; + final response = await http.post(Uri.parse(url), headers: headers, body: body); if (response.statusCode == 200) { return true; } @@ -188,42 +176,42 @@ Future requestTeamMember(String email, String token) async { Future updateTeamInfo( String name, String description, bool visible, String token) async { - String url = baseUrl + "team/"; + String url = "${baseUrl}team/"; Map headers = { "Content-type": "application/json", "x-access-token": token }; var body = json .encode({"name": name, "description": description, "visible": visible}); - await http.patch(url, headers: headers, body: body); //patch? + await http.patch(Uri.parse(url), headers: headers, body: body); //patch? } Future getTeamInfo(String teamId, String token) async { - String url = baseUrl + "team/" + teamId; + String url = "${baseUrl}team/$teamId"; Map headers = { "Content-type": "application/json", "x-access-token": token }; - final response = await http.get(url, headers: headers); + final response = await http.get(Uri.parse(url), headers: headers); if (response.statusCode == 200) { var data = jsonDecode(response.body); Team team = Team.fromJson(data); return team; } - return null; + throw Error(); } Future> getTeams(String token) async { - String url = baseUrl + "teams"; + String url = "${baseUrl}teams"; Map headers = { "Content-type": "application/json", "x-access-token": token }; print(token); - final response = await http.get(url, headers: headers); + final response = await http.get(Uri.parse(url), headers: headers); print(response.body); if (response.statusCode == 200) { List teamStrings = List.from(jsonDecode(response.body)); @@ -233,14 +221,14 @@ Future> getTeams(String token) async { } return teamsList; } - return null; + throw Error(); } //getStudents (in api) for the format of the request (particiants) //getTeam () for the mapping Future> teamSearch(String token, String query) async { //this is from the swagger - String url = baseUrl + "/teams/search?name=" + query; + String url = "$baseUrl/teams/search?name=$query"; Map headers = { "Content-type": "application/json", @@ -251,7 +239,7 @@ Future> teamSearch(String token, String query) async { //print(token); //look at swagger -H is the headers - final response = await http.get(url, headers: headers); + final response = await http.get(Uri.parse(url), headers: headers); //print(response.body); if (response.statusCode == 200) { List teamStrings = List.from(jsonDecode(response.body)); @@ -261,5 +249,5 @@ Future> teamSearch(String token, String query) async { } return teamsList; } - return null; + throw Error(); } diff --git a/lib/pages/teams_list.dart b/lib/pages/teams_list.dart index 78076bb..bf0e501 100644 --- a/lib/pages/teams_list.dart +++ b/lib/pages/teams_list.dart @@ -43,15 +43,14 @@ class TeamDetailsBtn extends StatelessWidget { context, MaterialPageRoute( builder: (context) => ViewTeam(), - settings: RouteSettings(arguments: team.teamID))), + settings: RouteSettings(arguments: team.teamID))), ); } } - class TeamJoinBtn extends StatelessWidget { final bool hasReqested; - final Function onJoinPressed; + final void Function() onJoinPressed; const TeamJoinBtn(this.hasReqested, this.onJoinPressed); @@ -76,7 +75,7 @@ class TeamJoinBtn extends StatelessWidget { class TeamEntryCard extends StatelessWidget { final Team team; final bool hasRequested; - final Function onJoinPressed; + final void Function() onJoinPressed; const TeamEntryCard(this.team, this.hasRequested, this.onJoinPressed); @@ -84,25 +83,25 @@ class TeamEntryCard extends StatelessWidget { Widget build(BuildContext context) { return Card( margin: const EdgeInsets.all(4), - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(team.name, style: Theme.of(context).textTheme.headline4), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TeamJoinBtn(hasRequested, onJoinPressed), - TeamDetailsBtn(team) - ], - ) - ], - ) - ), + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(team.name, + style: Theme.of(context).textTheme.headlineMedium), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TeamJoinBtn(hasRequested, onJoinPressed), + TeamDetailsBtn(team) + ], + ) + ], + )), ); } } @@ -113,7 +112,7 @@ class TeamHeader extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("TEAM", style: Theme.of(context).textTheme.headline2), + Text("TEAM", style: Theme.of(context).textTheme.displayMedium), IconButton( icon: const Icon(Icons.email, size: 30.0), color: Theme.of(context).colorScheme.tertiaryContainer, @@ -142,7 +141,7 @@ class _TeamsListState extends State { void search() async { fetchStatus = Status.notLoaded; SharedPreferences prefs = await SharedPreferences.getInstance(); - String token = prefs.getString('token'); + String token = prefs.getString('token') ?? ""; if (searchController.text != "") { teams = await teamSearch(token, searchController.text); @@ -152,9 +151,9 @@ class _TeamsListState extends State { teams.sort((a, b) => a.name.compareTo(b.name)); fetchStatus = Status.loaded; - setState(() { }); - + setState(() {}); } + @override initState() { fetchData(); @@ -164,33 +163,26 @@ class _TeamsListState extends State { Future fetchData() async { String token = Provider.of(context, listen: false).token; teams = await getTeams(token); - if (teams == null) { - errorDialog(context, 'Error', - 'We ran into an error while getting your team information. If the issue persists please contact a TartanHacks organizer.'); - fetchStatus = Status.error; - return; - } teams = teams.where((e) => e.visible).toList(); teams.sort((a, b) => a.name.compareTo(b.name)); - List requestsList = await getUserMail(token); - requestedTeams = requestsList?.map((e) => e['team']['_id'].toString())?.toSet() ?? {}; - fetchStatus = Status.loaded; - setState(() {}); // Reload user's team and redirect to ViewTeam page if user has a team await Provider.of(context, listen: false).fetchUserInfo(); bool hasTeam = Provider.of(context, listen: false).hasTeam; if (hasTeam) { Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => - ViewTeam(), - settings: const RouteSettings( - arguments: "" - ) - ) - ); + context, + MaterialPageRoute( + builder: (context) => ViewTeam(), + settings: const RouteSettings(arguments: ""))); + return; } + + List requestsList = await getUserMail(token); + requestedTeams = + requestsList.map((e) => e['team']['_id'].toString()).toSet(); + fetchStatus = Status.loaded; + setState(() {}); } @override @@ -205,7 +197,7 @@ class _TeamsListState extends State { alignment: Alignment.center, padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), child: RefreshIndicator( - onRefresh: (){ + onRefresh: () async { searchController.clear(); return fetchData(); }, @@ -226,8 +218,7 @@ class _TeamsListState extends State { Row(children: [ Expanded( child: TextField( - style: - Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, enableSuggestions: false, controller: searchController, textInputAction: TextInputAction.send, @@ -236,13 +227,12 @@ class _TeamsListState extends State { }, ), ), - const SizedBox( - width: 10 - ), + const SizedBox(width: 10), SolidButton( color: Theme.of(context).colorScheme.tertiary, onPressed: () { - FocusScopeNode currentFocus = FocusScope.of(context); + FocusScopeNode currentFocus = + FocusScope.of(context); if (!currentFocus.hasPrimaryFocus) { currentFocus.unfocus(); } @@ -254,17 +244,16 @@ class _TeamsListState extends State { .colorScheme .onTertiary)), ]), - const SizedBox( - height: 20 - ), + const SizedBox(height: 20), if (fetchStatus == Status.error) - ListRefreshable(child: const Center( - child: Text("Error loading teams") - ),) + ListRefreshable( + child: const Center( + child: Text("Error loading teams")), + ) else if (fetchStatus == Status.loaded && teams.isEmpty) - ListRefreshable(child: const Center( - child: Text("No teams available") - )) + ListRefreshable( + child: const Center( + child: Text("No teams available"))) else if (fetchStatus == Status.loaded) Expanded( child: ListView.builder( @@ -272,33 +261,38 @@ class _TeamsListState extends State { itemBuilder: (context, index) { Team team = teams[index]; String teamID = team.teamID; - bool hasReqested = requestedTeams.contains(teamID); - return TeamEntryCard( - teams[index], - hasReqested, + bool hasReqested = + requestedTeams.contains(teamID); + return TeamEntryCard(teams[index], hasReqested, () async { - OverlayEntry loading = LoadingOverlay(context); - Overlay.of(context).insert(loading); - String token = Provider.of(context, listen: false).token; - bool success = await requestTeam(teamID, token); - loading.remove(); - if (success) { - errorDialog(context, "Success", "A join request was sent to this team"); - requestedTeams.add(teamID); - setState(() {}); - } else { - errorDialog(context, "Error", "An error occurred with joining this team. Please try again."); - } - } - ); + OverlayEntry loading = + loadingOverlay(context); + Overlay.of(context).insert(loading); + String token = Provider.of( + context, + listen: false) + .token; + bool success = + await requestTeam(teamID, token); + loading.remove(); + if (success) { + errorDialog(context, "Success", + "A join request was sent to this team"); + requestedTeams.add(teamID); + setState(() {}); + } else { + errorDialog(context, "Error", + "An error occurred with joining this team. Please try again."); + } + }); }, ), ) else Center( child: CircularProgressIndicator( - color: Theme.of(context).colorScheme.onSurface) - ) + color: + Theme.of(context).colorScheme.onSurface)) ])), ))); } diff --git a/lib/pages/view_team.dart b/lib/pages/view_team.dart index e5d76c4..8a481c3 100644 --- a/lib/pages/view_team.dart +++ b/lib/pages/view_team.dart @@ -13,38 +13,36 @@ import 'see_invites.dart'; import 'teams_list.dart'; import 'create_team.dart'; -void showConfirmDialog(BuildContext context, String message, Function onConfirm) { +void showConfirmDialog( + BuildContext context, String message, void Function()? onConfirm) { showDialog( context: context, builder: (ctx) { return AlertDialog( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text("Confirmation", - style: Theme.of(context).textTheme.headline1), - content: Text( - message, - style: Theme.of(context).textTheme.bodyText2), - actions: [ - TextButton( - child: Text( - "Cancel", - style: Theme.of(context).textTheme.headline4, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + title: Text("Confirmation", + style: Theme.of(context).textTheme.displayLarge), + content: + Text(message, style: Theme.of(context).textTheme.bodyMedium), + actions: [ + TextButton( + child: Text( + "Cancel", + style: Theme.of(context).textTheme.headlineMedium, + ), + onPressed: () { + Navigator.of(context).pop(); + }, ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: Text( - "OK", - style: Theme.of(context).textTheme.headline4, + TextButton( + onPressed: onConfirm, + child: Text( + "OK", + style: Theme.of(context).textTheme.headlineMedium, + ), ), - onPressed: onConfirm, - ), - ] - ); - } - ); + ]); + }); } class InviteMembersBtn extends StatelessWidget { @@ -55,21 +53,22 @@ class InviteMembersBtn extends StatelessWidget { // Show invitation dialog Widget _inviteMessage(BuildContext context) { TextEditingController inviteController = TextEditingController(); - String emailInvite; + String emailInvite = ""; return AlertDialog( backgroundColor: Theme.of(context).scaffoldBackgroundColor, - title: Text('Send Invite', style: Theme.of(context).textTheme.headline1), + title: + Text('Send Invite', style: Theme.of(context).textTheme.displayLarge), content: TextFormField( decoration: const InputDecoration(labelText: "email"), - style: Theme.of(context).textTheme.bodyText2, + style: Theme.of(context).textTheme.bodyMedium, keyboardType: TextInputType.emailAddress, controller: inviteController, - validator: (String value) { - if (value.isEmpty) { + validator: (value) { + if (value == null || value.isEmpty) { return 'An email is required'; } - return null; + throw Error(); }, onChanged: (String value) { emailInvite = value; @@ -79,7 +78,7 @@ class InviteMembersBtn extends StatelessWidget { TextButton( child: Text( "Cancel", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () { Navigator.of(context).pop(); @@ -88,7 +87,7 @@ class InviteMembersBtn extends StatelessWidget { TextButton( child: Text( "Send", - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineMedium, ), onPressed: () async { Navigator.of(context).pop(); @@ -131,30 +130,26 @@ class LeaveJoinTeamBtn extends StatelessWidget { bool canLeave = team.members.length == 1 || !isAdmin; if (!canLeave) { errorDialog(context, "Cannot Leave Team", - "You must promote someone else to an admin before you leave the team" - ); + "You must promote someone else to an admin before you leave the team"); return; } showConfirmDialog( - context, - "Are you sure want to leave your team? Your team information may be " - "lost if you are the only member left.", - () async { - bool success = await leaveTeam(token); - if (!success) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Error leaving team."), - )); - return; - } - await Provider.of(context, listen: false).fetchUserInfo(); - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => TeamsList()) - ); - } - ); + context, + "Are you sure want to leave your team? Your team information may be " + "lost if you are the only member left.", () async { + bool success = await leaveTeam(token); + if (!success) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text("Error leaving team."), + )); + return; + } + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => TeamsList()), + (route) => route.isFirst); + }); }, color: Theme.of(context).colorScheme.tertiaryContainer); } @@ -170,47 +165,65 @@ class MemberListElement extends StatelessWidget { @override Widget build(BuildContext context) { String id = mem.id; - String emailStr = "(" + mem.email + ")"; + String emailStr = "(${mem.email})"; String nameStr = mem.name; bool isMemAdmin = adminIds.contains(id); return Container( padding: const EdgeInsets.fromLTRB(0, 0, 0, 5), - child: - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - text: TextSpan(children: [ - TextSpan( - text: nameStr + " ", - style: Theme.of(context).textTheme.bodyText2), - if (isMemAdmin) - WidgetSpan( - child: Icon(Icons.star, - size: 20, - color: Theme.of(context).colorScheme.tertiaryContainer)) - ])), - Text(emailStr, style: Theme.of(context).textTheme.bodyText2) - ]), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + overflow: TextOverflow.ellipsis, + text: TextSpan(children: [ + TextSpan( + text: nameStr, + style: Theme.of(context).textTheme.bodyMedium), + if (isMemAdmin) + WidgetSpan( + child: Icon(Icons.star, + size: 20, + color: Theme.of(context) + .colorScheme + .tertiaryContainer)) + ])), + Text( + emailStr, + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.ellipsis, + ) + ]), + ), if (isAdmin && !isMemAdmin) - SolidButton( - text: "Promote", - color: Theme.of(context).colorScheme.secondary, - onPressed: () { - showConfirmDialog(context, "Are you sure you want to promote this member to an admin? You will no longer be an admin.", () { - String token = Provider.of(context, listen: false).token; - promoteToAdmin(id, token).then((_) { - Navigator.of(context).pop(); - Provider.of(context, listen: false) - .fetchUserInfo(); - }); - }); - }, - ) - ])); + Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), + child: SolidButton( + text: "Promote", + color: Theme.of(context).colorScheme.secondary, + onPressed: () { + showConfirmDialog(context, + "Are you sure you want to promote this member to an admin? You will no longer be an admin.", + () { + String token = + Provider.of(context, listen: false) + .token; + promoteToAdmin(id, token).then((_) { + Navigator.of(context).pop(); + Provider.of(context, listen: false) + .fetchUserInfo(); + }); + }); + }, + ), + ) + ])); } } @@ -227,7 +240,8 @@ class TeamMembersList extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Team Members", style: Theme.of(context).textTheme.headline4), + Text("Team Members", + style: Theme.of(context).textTheme.headlineMedium), Column( crossAxisAlignment: CrossAxisAlignment.start, children: team.members @@ -244,10 +258,7 @@ class TeamMail extends StatelessWidget { alignment: Alignment.centerRight, child: IconButton( icon: const Icon(Icons.email, size: 30.0), - color: Theme - .of(context) - .colorScheme - .tertiaryContainer, + color: Theme.of(context).colorScheme.tertiaryContainer, onPressed: () { Navigator.push( context, @@ -266,8 +277,9 @@ class TeamHeader extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( height: 50, - child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text("TEAM", style: Theme.of(context).textTheme.headline1), + child: + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text("TEAM", style: Theme.of(context).textTheme.displayLarge), isAdmin ? TeamMail() : Container() ])); } @@ -284,9 +296,8 @@ class TeamDesc extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(team.name ?? "", style: Theme.of(context).textTheme.headline4), - Text(team.description ?? "", - style: Theme.of(context).textTheme.bodyText2) + Text(team.name, style: Theme.of(context).textTheme.headlineMedium), + Text(team.description, style: Theme.of(context).textTheme.bodyMedium) ]); } } @@ -309,28 +320,28 @@ class EditTeamButton extends StatelessWidget { class OwnTeamView extends StatelessWidget { @override Widget build(BuildContext context) { - Team team = Provider.of(context).team; + Team? team = Provider.of(context).team; String id = Provider.of(context).id; Status status = Provider.of(context).userInfoStatus; - List adminIds = team.admins.map((mem) => mem.id).toList(); - bool isAdmin = adminIds.contains(id); + List? adminIds = team?.admins.map((mem) => mem.id).toList(); + bool? isAdmin = adminIds?.contains(id); return status == Status.loaded ? Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - TeamHeader(isAdmin), + TeamHeader(isAdmin!), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), - child: TeamDesc(team)), + child: TeamDesc(team!)), isAdmin ? EditTeamButton() : Container(), Container( padding: const EdgeInsets.fromLTRB(0, 5, 0, 5), - child: TeamMembersList(team, isAdmin, adminIds)), + child: TeamMembersList(team, isAdmin, adminIds!)), if (isAdmin && team.members.length < 4) InviteMembersBtn(team), const SizedBox( @@ -343,8 +354,8 @@ class OwnTeamView extends StatelessWidget { ) : const Center( child: Padding( - child: CircularProgressIndicator(), padding: EdgeInsets.symmetric(vertical: 50), + child: CircularProgressIndicator(), )); } } @@ -362,7 +373,7 @@ class BrowseTeamView extends StatelessWidget { future: getTeamInfo(teamId, token), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - Team team = snapshot.data; + Team team = snapshot.data as Team; // TODO: this is sus please check. String id = Provider.of(context).id; List adminIds = team.admins.map((mem) => mem.id).toList(); bool isAdmin = adminIds.contains(id); @@ -388,18 +399,18 @@ class BrowseTeamView extends StatelessWidget { } else if (snapshot.hasError) { return Center( child: Padding( + padding: const EdgeInsets.symmetric(vertical: 50), child: Text( "Could not load team data", - style: Theme.of(context).textTheme.headline1, + style: Theme.of(context).textTheme.displayLarge, ), - padding: const EdgeInsets.symmetric(vertical: 50), ), ); } else { return const Center( child: Padding( - child: CircularProgressIndicator(), padding: EdgeInsets.symmetric(vertical: 50), + child: CircularProgressIndicator(), )); } }, @@ -416,7 +427,7 @@ class ViewTeam extends StatelessWidget { String teamId = ""; if (ModalRoute.of(context) != null) { - teamId = ModalRoute.of(context).settings.arguments as String; + teamId = ModalRoute.of(context)?.settings.arguments as String? ?? ""; } return DefaultPage( @@ -430,9 +441,12 @@ class ViewTeam extends StatelessWidget { height: screenHeight * 0.75, padding: const EdgeInsets.fromLTRB(20, 10, 20, 10), alignment: Alignment.topLeft, - child: SingleChildScrollView( - child: teamId == "" - ? OwnTeamView() - : BrowseTeamView(teamId))))); + child: SingleChildScrollView(child: () { + if (teamId == "") { + return OwnTeamView(); + } else { + return BrowseTeamView(teamId); + } + }())))); } } diff --git a/lib/providers/check_in_items_provider.dart b/lib/providers/check_in_items_provider.dart index 5f5f7df..973716f 100644 --- a/lib/providers/check_in_items_provider.dart +++ b/lib/providers/check_in_items_provider.dart @@ -16,11 +16,11 @@ class CheckInItemsModel with ChangeNotifier { String _token = ""; String _uid = ""; - List _list; - Map hasCheckedIn; + List _list = []; + late Map? hasCheckedIn; - bool isAdmin; - int points; + late bool isAdmin; + late int? points; Status get checkInItemsStatus => _status; List get checkInItems => _list; @@ -36,9 +36,9 @@ class CheckInItemsModel with ChangeNotifier { final SharedPreferences prefs = await SharedPreferences.getInstance(); - _token = prefs.getString("token"); - _uid = prefs.getString("id"); - isAdmin = prefs.getBool("admin"); + _token = prefs.getString("token") ?? ""; + _uid = prefs.getString("id") ?? ""; + isAdmin = prefs.getBool("admin") ?? false; _status = Status.fetching; try { @@ -47,7 +47,7 @@ class CheckInItemsModel with ChangeNotifier { hasCheckedIn = null; points = 0; } else { - List history = await api.getUserHistory(prefs.getString("id"), _token); + List history = await api.getUserHistory(prefs.getString("id")!, _token); points = history[0]; hasCheckedIn = history[1]; _list = history[2]; @@ -86,7 +86,7 @@ class CheckInItemsModel with ChangeNotifier { void reset() { _status = Status.notLoaded; - _list = null; + _list = []; _uid = ""; hasCheckedIn = null; notifyListeners(); diff --git a/lib/providers/expo_config_provider.dart b/lib/providers/expo_config_provider.dart new file mode 100644 index 0000000..3799322 --- /dev/null +++ b/lib/providers/expo_config_provider.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import '../models/config.dart'; +import '../api.dart'; + +class ExpoConfigProvider with ChangeNotifier { + ExpoConfig? _config; + bool _loading = false; + String? _error; + + ExpoConfig? get config => _config; + bool get loading => _loading; + String? get error => _error; + + Future loadConfig(String token) async { + _loading = true; + _error = null; + notifyListeners(); + + try { + _config = await getExpoConfig(token); + } catch (e) { + _error = e.toString(); + } finally { + _loading = false; + notifyListeners(); + } + } + + bool canSubmitTableNumber() { + if (_config == null) return false; + return DateTime.now().isBefore(_config!.expoStartTime); + } +} \ No newline at end of file diff --git a/lib/providers/user_info_provider.dart b/lib/providers/user_info_provider.dart index d5dd61c..72a9eb1 100644 --- a/lib/providers/user_info_provider.dart +++ b/lib/providers/user_info_provider.dart @@ -17,8 +17,8 @@ class UserInfoModel with ChangeNotifier { String _token = ""; String _uid = ""; - Profile userProfile; - Team team; + Profile? userProfile; + Team? team; bool hasTeam = false; bool isAdmin = false; @@ -34,9 +34,9 @@ class UserInfoModel with ChangeNotifier { Future fetchUserInfo() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - _token = prefs.getString("token"); - _uid = prefs.getString("id"); - isAdmin = prefs.getBool("admin"); + _token = prefs.getString("token")!; + _uid = prefs.getString("id")!; + isAdmin = prefs.getBool("admin")!; if (_status == Status.notLoaded) { _status = Status.fetching; } diff --git a/lib/theme_changer.dart b/lib/theme_changer.dart index 1de8601..85e9cd2 100644 --- a/lib/theme_changer.dart +++ b/lib/theme_changer.dart @@ -1,53 +1,53 @@ import 'package:flutter/material.dart'; - -ThemeData baseTheme({ColorScheme cScheme, Color background, Color text}) { +ThemeData baseTheme( + {required ColorScheme cScheme, + required Color background, + required Color text}) { return ThemeData( fontFamily: 'Futura Md BT', colorScheme: cScheme, scaffoldBackgroundColor: background, - accentColor: text, + secondaryHeaderColor: text, textTheme: TextTheme( - headline1: TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold, color: text), - headline2: TextStyle(fontSize: 26.0, fontWeight: FontWeight.bold, color: text), - headline3: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold, color: text), - headline4: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold, color: text), - bodyText1: TextStyle(fontSize: 14.0, fontWeight: FontWeight.w600, color: background), - bodyText2: TextStyle(fontSize: 16.0, color: text), + displayLarge: + TextStyle(fontSize: 30.0, fontWeight: FontWeight.bold, color: text), + displayMedium: + TextStyle(fontSize: 26.0, fontWeight: FontWeight.bold, color: text), + displaySmall: + TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold, color: text), + headlineMedium: + TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold, color: text), + bodyLarge: TextStyle( + fontSize: 14.0, fontWeight: FontWeight.w600, color: background), + bodyMedium: TextStyle(fontSize: 16.0, color: text), ), inputDecorationTheme: InputDecorationTheme( - labelStyle: TextStyle(color: text, fontWeight: FontWeight.bold), - errorStyle: TextStyle(color: cScheme.secondary), - isDense: true, - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(width: 2.0, color: text), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(width: 2.0, color: text), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) - ), - disabledBorder: OutlineInputBorder( - borderSide: BorderSide(width: 2.0, color: text.withAlpha(87)), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(width: 2.0, color: text), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) - ), + labelStyle: TextStyle(color: text, fontWeight: FontWeight.bold), + errorStyle: TextStyle(color: cScheme.secondary), + isDense: true, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(width: 2.0, color: text), + borderRadius: const BorderRadius.all(Radius.circular(15.0))), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(width: 2.0, color: text), + borderRadius: const BorderRadius.all(Radius.circular(15.0))), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide(width: 2.0, color: text.withAlpha(87)), + borderRadius: const BorderRadius.all(Radius.circular(15.0))), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(width: 2.0, color: text), + borderRadius: const BorderRadius.all(Radius.circular(15.0))), focusedErrorBorder: OutlineInputBorder( borderSide: BorderSide(width: 2.0, color: text), - borderRadius: const BorderRadius.all(Radius.circular(15.0)) - ), - ) - ); + borderRadius: const BorderRadius.all(Radius.circular(15.0))), + )); } -ThemeData genLightTheme (){ - - var primary = const Color(0xFF1B1818); - var secondary = const Color(0xFF1B1818); - var buttons = const Color(0xFF1B1818); +ThemeData genLightTheme() { + var primary = const Color(0xFF0B3B48); + var secondary = const Color(0xFF0B3B48); + var buttons = const Color(0xFF0B3B48); var altbuttons = const Color(0xFF1028F1); var surface = const Color(0xFFE8EAEF); var surface2 = const Color(0xFFA8C2fE); @@ -60,8 +60,7 @@ ThemeData genLightTheme (){ tertiary: buttons, //main button color tertiaryContainer: altbuttons, //alt button color surface: surface, //box gradient 1 - surfaceTint: surface2, //box gradient 2 - background: background, + surfaceTint: surface2, error: altbuttons, errorContainer: secondary, //menu buttons onPrimary: background, @@ -69,21 +68,14 @@ ThemeData genLightTheme (){ onTertiary: surface, onTertiaryContainer: surface, onSurface: secondary, - onBackground: secondary, onError: secondary, onErrorContainer: background, shadow: shadow, - brightness: Brightness.light - ); - return baseTheme( - cScheme: cScheme, - background: background, - text: secondary - ); + brightness: Brightness.light); + return baseTheme(cScheme: cScheme, background: background, text: secondary); } -ThemeData genDarkTheme (){ - +ThemeData genDarkTheme() { var primary = const Color(0xFF1028F1); var secondary = const Color(0xFF735FFF); var buttons = const Color(0xFFFF7AA5); @@ -100,8 +92,7 @@ ThemeData genDarkTheme (){ tertiary: buttons, //main button color tertiaryContainer: altbuttons, //alt button color surface: surface, //box gradient 1 - surfaceTint: surface2, //box gradient 2 - background: background, + surfaceTint: surface2, error: buttons, errorContainer: primary, //menu buttons onPrimary: text, @@ -110,16 +101,14 @@ ThemeData genDarkTheme (){ onTertiaryContainer: background, onSurface: text, onSurfaceVariant: text, - onBackground: text, onError: text, onErrorContainer: text, shadow: shadow, - brightness: Brightness.light - ); + brightness: Brightness.light); return baseTheme( - cScheme: cScheme, - background: background, - text: text, + cScheme: cScheme, + background: background, + text: text, ); } @@ -135,4 +124,4 @@ class ThemeChanger extends ChangeNotifier { _themeData = theme; notifyListeners(); } -} \ No newline at end of file +} diff --git a/pubspec.lock b/pubspec.lock index e56cc6d..3c3a33f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,111 +1,158 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - archive: + args: dependency: transitive description: - name: archive - url: "https://pub.dartlang.org" + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "2.6.0" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" carousel_slider: dependency: "direct main" description: name: carousel_slider - url: "https://pub.dartlang.org" + sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae" + url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "5.0.0" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" charcode: dependency: "direct main" description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + url: "https://pub.dev" source: hosted - version: "1.16.0" - crypto: + version: "1.19.0" + cross_file: dependency: transitive description: - name: crypto - url: "https://pub.dartlang.org" + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "0.3.4+2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "1.0.8" date_time_picker: dependency: "direct main" description: name: date_time_picker - url: "https://pub.dartlang.org" + sha256: "6923c568bcb67a66ab7e083708d0adbcae8214b41bb84d49febc17e89e06fc4a" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "2.1.0" decorated_icon: dependency: "direct main" description: name: decorated_icon - url: "https://pub.dartlang.org" + sha256: "4e2af1906cd211905ee729960daef162cd9f0bacd1d24de99e84e37e114a3648" + url: "https://pub.dev" source: hosted version: "1.2.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.3" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "7.0.0" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" flutter: dependency: "direct main" description: flutter @@ -115,9 +162,10 @@ packages: dependency: "direct main" description: name: flutter_countdown_timer - url: "https://pub.dartlang.org" + sha256: dfcbd7d6f76a5589f78f3f3ba2f9ea2e199368eccc1adce4153ce985b9587bc5 + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "4.1.0" flutter_driver: dependency: transitive description: flutter @@ -127,30 +175,34 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "3.0.2" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.24" flutter_smart_scan: dependency: "direct main" description: name: flutter_smart_scan - url: "https://pub.dartlang.org" + sha256: "3232ab12c11d18c42132e909182221b9b1ef006264e36bd342ae78b126f990d2" + url: "https://pub.dev" source: hosted version: "1.0.4" flutter_svg: dependency: "direct main" description: name: flutter_svg - url: "https://pub.dartlang.org" + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + url: "https://pub.dev" source: hosted - version: "0.22.0" + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -170,30 +222,106 @@ packages: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" source: hosted - version: "0.12.2" + version: "1.3.0" http_parser: - dependency: transitive + dependency: "direct main" description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "4.1.2" image_cropper: dependency: "direct main" description: name: image_cropper - url: "https://pub.dartlang.org" + sha256: "266760ed426d7121f0ada02c672bfe5c1b5c714e908328716aee756f045709dc" + url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "8.1.0" + image_cropper_for_web: + dependency: transitive + description: + name: image_cropper_for_web + sha256: "34256c8fb7fcb233251787c876bb37271744459b593a948a2db73caa323034d0" + url: "https://pub.dev" + source: hosted + version: "6.0.2" + image_cropper_platform_interface: + dependency: transitive + description: + name: image_cropper_platform_interface + sha256: e8e9d2ca36360387aee39295ce49029362ae4df3071f23e8e71f2b81e40b7531 + url: "https://pub.dev" + source: hosted + version: "7.0.0" image_picker: dependency: "direct main" description: name: image_picker - url: "https://pub.dartlang.org" + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c + url: "https://pub.dev" + source: hosted + version: "0.8.12+20" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" source: hosted - version: "0.6.2+3" + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" integration_test: dependency: "direct dev" description: flutter @@ -203,357 +331,455 @@ packages: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" source: hosted - version: "0.16.1" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.4" + version: "0.17.0" json_annotation: dependency: "direct main" description: name: json_annotation - url: "https://pub.dartlang.org" + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + url: "https://pub.dev" + source: hosted + version: "10.0.7" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.0.8" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "3.0.0" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.11.1" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" source: hosted - version: "0.9.7" + version: "1.0.6" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" source: hosted version: "1.0.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.2" - path_drawing: - dependency: transitive - description: - name: path_drawing - url: "https://pub.dartlang.org" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" source: hosted - version: "0.5.1+1" + version: "1.9.0" path_parsing: dependency: transitive description: name: path_parsing - url: "https://pub.dartlang.org" + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "1.1.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" source: hosted - version: "0.0.1+2" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" source: hosted - version: "0.0.5" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.11.1" + version: "2.3.0" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "6.0.2" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.1.8" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.2" provider: dependency: "direct main" description: name: provider - url: "https://pub.dartlang.org" + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" source: hosted - version: "6.0.5" + version: "6.1.2" qr: dependency: transitive description: name: qr - url: "https://pub.dartlang.org" + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "3.0.2" qr_flutter: dependency: "direct main" description: name: qr_flutter - url: "https://pub.dartlang.org" + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.1.0" share: dependency: "direct main" description: name: share - url: "https://pub.dartlang.org" + sha256: "97e6403f564ed1051a01534c2fc919cb6e40ea55e60a18ec23cee6e0ce19f4be" + url: "https://pub.dev" source: hosted - version: "0.6.5+4" + version: "2.0.4" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: c59819dacc6669a1165d54d2735a9543f136f9b3cec94ca65cea6ab8dffc422e + url: "https://pub.dev" source: hosted - version: "0.5.12+4" - shared_preferences_linux: + version: "2.4.0" + shared_preferences_android: dependency: transitive description: - name: shared_preferences_linux - url: "https://pub.dartlang.org" + name: shared_preferences_android + sha256: "986dc7b7d14f38064bfa85ace28df1f1a66d4fba32e4b1079d4ea537d9541b01" + url: "https://pub.dev" source: hosted - version: "0.0.2+4" - shared_preferences_macos: + version: "2.4.3" + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" source: hosted - version: "0.0.1+11" + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" source: hosted - version: "0.1.2+7" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" source: hosted - version: "0.0.2+3" + version: "2.4.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.3.0" sync_http: dependency: transitive description: name: sync_http - url: "https://pub.dartlang.org" + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" source: hosted version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.7.3" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.4.0" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" source: hosted - version: "5.7.10" + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: "direct main" + description: + name: url_launcher_ios + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + url: "https://pub.dev" + source: hosted + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" source: hosted - version: "0.0.1+4" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" source: hosted - version: "0.0.1+9" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" source: hosted - version: "1.0.9" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + url: "https://pub.dev" source: hosted - version: "0.1.5+3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" + url: "https://pub.dev" + source: hosted + version: "1.1.15" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" source: hosted - version: "0.0.1+3" + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + url: "https://pub.dev" + source: hosted + version: "1.1.16" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - url: "https://pub.dartlang.org" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + url: "https://pub.dev" source: hosted - version: "9.0.0" - webdriver: + version: "14.3.0" + web: dependency: transitive description: - name: webdriver - url: "https://pub.dartlang.org" + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" source: hosted - version: "3.0.0" - win32: + version: "1.1.0" + webdriver: dependency: transitive description: - name: win32 - url: "https://pub.dartlang.org" + name: webdriver + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "3.0.4" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "1.1.0" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" source: hosted - version: "5.4.1" + version: "6.5.0" sdks: - dart: ">=2.17.0 <3.0.0" - flutter: ">=2.8.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index a33cf12..732116c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,10 +11,10 @@ description: TartanHacks app # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 3.0.1+14 +version: 5.0.0+1 environment: - sdk: ">=2.5.2 <3.0.0" + sdk: ">=2.12.2 <3.0.0" dependencies: flutter: @@ -24,26 +24,28 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 - http: ^0.12.2 - shared_preferences: ^0.5.3+1 - url_launcher: ^5.0.3 - flutter_countdown_timer: ^3.0.1 - intl: ^0.16.1 - share: ^0.6.5+4 - date_time_picker: ^1.1.1 - qr_flutter: ^3.1.0 + cupertino_icons: ^1.0.6 + http: ^1.2.0 + shared_preferences: ^2.2.2 + url_launcher: ^6.3.1 + url_launcher_ios: ^6.3.2 + flutter_countdown_timer: ^4.1.0 + intl: ^0.17.0 + share: ^2.0.4 + date_time_picker: ^2.1.0 + qr_flutter: ^4.1.0 flutter_smart_scan: ^1.0.4 - json_annotation: ^3.1.1 + json_annotation: ^4.8.1 charcode: ^1.2.0 - carousel_slider: ^2.3.1 - flutter_svg: ^0.22.0 + carousel_slider: ^5.0.0 + flutter_svg: ^2.0.9 decorated_icon: ^1.2.1 image_picker: - image_cropper: ^1.5.0 + image_cropper: ^8.1.0 + http_parser: any dev_dependencies: - flutter_lints: ^1.0.4 + flutter_lints: ^3.0.1 flutter_test: sdk: flutter integration_test: