├── .fvm └── fvm_config.json ├── .fvmrc ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── ostad │ │ │ │ └── task_manager │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── fonts │ ├── Poppins-Bold.ttf │ └── Poppins-Regular.ttf └── images │ ├── background.svg │ ├── darkBackground.svg │ ├── empty.svg │ ├── logo.svg │ ├── noInternet.svg │ └── user.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── app │ └── app.dart ├── main.dart ├── models │ ├── loginModels │ │ ├── login_model.dart │ │ └── user_data.dart │ ├── responseModel │ │ ├── failure.dart │ │ ├── response_code.dart │ │ └── success.dart │ ├── taskListModel │ │ ├── task_data.dart │ │ └── task_list_model.dart │ └── taskStatusCountModels │ │ ├── status_data.dart │ │ └── task_status_count_model.dart ├── services │ ├── auth_service.dart │ ├── connectivity_checker.dart │ ├── network_request.dart │ ├── task_service.dart │ └── user_info_service.dart ├── themes │ ├── app_elevated_button_style.dart │ ├── app_text_style.dart │ ├── app_textfield_style.dart │ ├── app_theme.dart │ ├── appbar_style.dart │ ├── card_style.dart │ ├── expansion_tile_style.dart │ ├── navigation_bar_style.dart │ ├── popup_menu_style.dart │ └── theme_changer.dart ├── utils │ ├── app_assets.dart │ ├── app_color.dart │ ├── app_navigation.dart │ ├── app_routes.dart │ └── app_strings.dart ├── viewModels │ ├── auth_view_model.dart │ ├── countdown_timer_view_model.dart │ ├── dashboard_view_model.dart │ ├── task_view_model.dart │ └── user_view_model.dart ├── views │ ├── authScreens │ │ ├── forgetPasswordScreen │ │ │ ├── emailVerificationScreen │ │ │ │ └── email_verification_screen.dart │ │ │ ├── pinVerificationScreen │ │ │ │ ├── pin_verification_form.dart │ │ │ │ ├── pin_verification_screen.dart │ │ │ │ └── resend_pin_layout.dart │ │ │ └── setPasswordScreen │ │ │ │ └── set_password_screen.dart │ │ ├── signInScreen │ │ │ ├── sign_in_screen.dart │ │ │ └── sign_in_screen_form.dart │ │ └── signUpScreen │ │ │ ├── sign_up_form.dart │ │ │ └── sign_up_screen.dart │ ├── dashboardScreen │ │ └── dashboard_screen.dart │ ├── newTaskAddScreen │ │ ├── add_task_screen.dart │ │ └── new_task_add_screen.dart │ ├── splashScreen │ │ └── splash_screen.dart │ ├── taskCancelledScreen │ │ └── task_cancelled_screen.dart │ ├── taskCompletedScreen │ │ └── task_completed_screen.dart │ ├── taskProgressScreen │ │ └── task_progress_screen.dart │ ├── updateProfileScreen │ │ ├── update_profile_screen.dart │ │ └── update_profile_screen_form.dart │ └── widgets │ │ ├── app_bar.dart │ │ ├── app_snackbar.dart │ │ ├── app_textfield.dart │ │ ├── background_widget.dart │ │ ├── circular_progressbar.dart │ │ ├── fallback_widget.dart │ │ ├── forget_password_layout.dart │ │ ├── loading_layout.dart │ │ ├── sign_in_bottom_text.dart │ │ ├── task_list_card.dart │ │ └── task_status_card.dart └── wrappers │ ├── svg_image_loader.dart │ └── widget_custom_animator.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.fvm/fvm_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "flutterSdkVersion": "3.22.2" 3 | } -------------------------------------------------------------------------------- /.fvmrc: -------------------------------------------------------------------------------- 1 | { 2 | "flutter": "3.22.2" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | 45 | # FVM Version Cache 46 | .fvm/ -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 17 | base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 18 | - platform: android 19 | create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 20 | base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 21 | - platform: ios 22 | create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 23 | base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Task Manager 2 | coding
3 |  

This Flutter application is a comprehensive task manager designed to empower users to stay organized and achieve their goals effectively. It offers a suite of features that cater to various task management needs, built with a focus on security and user experience.

4 | 5 |
6 | 7 | ## Project APK Link 8 | `Download link-` https://shorturl.at/563SN 9 |
10 | 11 | ## Installation 12 | ### Git Clone 13 | ``` 14 | git clone https://github.com/Nafis71/task_manager.git 15 | ``` 16 | ### Flutter Version Management 17 | ``` 18 | fvm use 19 | ``` 20 | ### Pub Get 21 | ``` 22 | fvm flutter pub get 23 | ``` 24 | *or* 25 | ``` 26 | flutter pub get 27 | ``` 28 | ### Build Apk 29 | ``` 30 | fvm flutter build apk --release 31 | ``` 32 | 33 | ## Tech Stack 34 | 35 | coding 36 |
37 | 38 | - Flutter 39 | - Dart 40 | - Rest API 41 | - Provider (State Management)
42 | 43 | 44 | 45 |
46 |
47 | 48 | ## Screenshots 49 | `1. Splash Screen and Authentication`
50 | > Streamlined signup and login process. 51 | Robust JWT authentication for secure data access. 52 | Enhanced security with OTP verification for resetting password. 53 | User-friendly password reset functionality with forgotten password support. 54 |
55 | 56 | `Light Mode` 57 | 58 | homePage 59 | homePage 60 | homePage 61 |
62 | 63 | `Dark Mode` 64 | 65 | homePage 66 | homePage 67 | homePage 68 | 69 |
70 | 71 | `2. Forget Password`
72 | > With the use of OTP verification and forget password, users can reset their passwords 73 |
74 | 75 | `Light Mode` 76 | 77 | homePage 78 | homePage 79 | homePage 80 |
81 | 82 | `Dark Mode` 83 | 84 | homePage 85 | homePage 86 | homePage 87 |
88 | 89 | `3. Task Management`
90 | > Create new tasks with clear details. 91 | Modify existing tasks to reflect progress, mark them as completed, or cancel them as needed. 92 | Effortlessly delete tasks when they're no longer relevant. 93 | Maintain an organized task list for optimal productivity. 94 |
95 | 96 | `Light Mode` 97 | 98 | homePage 99 | homePage 100 | homePage 101 | homePage 102 |
103 | 104 | `Dark Mode` 105 | 106 | homePage 107 | homePage 108 | homePage 109 | homePage 110 |
111 | 112 | `4. Profile Update and New Task Addition`
113 | > Users are able to edit their profile information and add new task. 114 |
115 | 116 | `Light Mode` 117 | 118 | homePage 119 | homePage 120 |
121 | 122 | `Dark Mode` 123 | 124 | homePage 125 | homePage 126 |
127 | 128 | `4. Internet connection observer`
129 | > Users will get notified if theres any internet issue. 130 |
131 | 132 | homePage 133 | homePage 134 |
135 | 136 |

This task manager app is ideal for individuals seeking a convenient and secure solution to manage their to-dos, from students juggling busy schedules to professionals prioritizing projects.

137 |
138 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | def localProperties = new Properties() 9 | def localPropertiesFile = rootProject.file("local.properties") 10 | if (localPropertiesFile.exists()) { 11 | localPropertiesFile.withReader("UTF-8") { reader -> 12 | localProperties.load(reader) 13 | } 14 | } 15 | 16 | def flutterVersionCode = localProperties.getProperty("flutter.versionCode") 17 | if (flutterVersionCode == null) { 18 | flutterVersionCode = "1" 19 | } 20 | 21 | def flutterVersionName = localProperties.getProperty("flutter.versionName") 22 | if (flutterVersionName == null) { 23 | flutterVersionName = "1.0" 24 | } 25 | 26 | android { 27 | namespace = "com.ostad.task_manager" 28 | compileSdk = flutter.compileSdkVersion 29 | ndkVersion = flutter.ndkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_1_8 33 | targetCompatibility = JavaVersion.VERSION_1_8 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId = "com.ostad.task_manager" 39 | // You can update the following values to match your application needs. 40 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 41 | minSdk = flutter.minSdkVersion 42 | targetSdk = flutter.targetSdkVersion 43 | versionCode = flutterVersionCode.toInteger() 44 | versionName = flutterVersionName 45 | } 46 | 47 | buildTypes { 48 | release { 49 | // TODO: Add your own signing config for the release build. 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig = signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source = "../.." 58 | } 59 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 17 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/ostad/task_manager/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ostad.task_manager; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.3.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /assets/fonts/Poppins-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/assets/fonts/Poppins-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Poppins-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/assets/fonts/Poppins-Regular.ttf -------------------------------------------------------------------------------- /assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 33 | 34 | -------------------------------------------------------------------------------- /assets/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/assets/images/user.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nafis71/task_manager/288b95e90f66a97afa453b58fc7062277ae380ce/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Task Manager 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | task_manager 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:device_preview/device_preview.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:task_manager/services/connectivity_checker.dart'; 5 | import 'package:task_manager/themes/app_theme.dart'; 6 | import 'package:task_manager/themes/theme_changer.dart'; 7 | import 'package:task_manager/utils/app_routes.dart'; 8 | import 'package:task_manager/utils/app_strings.dart'; 9 | import 'package:task_manager/viewModels/auth_view_model.dart'; 10 | import 'package:task_manager/viewModels/countdown_timer_view_model.dart'; 11 | import 'package:task_manager/viewModels/dashboard_view_model.dart'; 12 | import 'package:task_manager/viewModels/task_view_model.dart'; 13 | import 'package:task_manager/viewModels/user_view_model.dart'; 14 | 15 | class TaskManager extends StatelessWidget { 16 | static final GlobalKey navigatorKey = 17 | GlobalKey(); 18 | final String userTheme; 19 | 20 | const TaskManager({super.key, required this.userTheme}); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return MultiProvider( 25 | providers: [ 26 | ChangeNotifierProvider(create: (_) => AuthViewModel()), 27 | ChangeNotifierProvider(create: (_) => UserViewModel()), 28 | ChangeNotifierProvider(create: (_) => DashboardViewModel()), 29 | ChangeNotifierProvider(create: (_) => TaskViewModel()), 30 | ChangeNotifierProvider(create: (_) => CountdownTimerViewModel()), 31 | ChangeNotifierProvider(create: (_) => ThemeChanger()), 32 | ChangeNotifierProvider(create: (_) => ConnectivityChecker()), 33 | ], 34 | child: Builder(builder: (context) { 35 | if (!context.read().isAppLaunched) { 36 | loadUserTheme(userTheme, context); 37 | } 38 | context.read().setIsAppLaunched = true; 39 | context.read().initConnectivityChecker(); 40 | return MaterialApp( 41 | navigatorKey: navigatorKey, 42 | debugShowCheckedModeBanner: false, 43 | initialRoute: AppRoutes.splashScreen, 44 | locale: DevicePreview.locale(context), 45 | builder: DevicePreview.appBuilder, 46 | onGenerateRoute: (routeSettings) { 47 | return AppRoutes.generateRoute(routeSettings); 48 | }, 49 | themeMode: context.watch().themeMode, 50 | theme: AppTheme.getLightTheme(), 51 | darkTheme: AppTheme.getDarkTheme(), 52 | ); 53 | }), 54 | ); 55 | } 56 | 57 | void loadUserTheme(String theme, BuildContext context) { 58 | switch (theme) { 59 | case AppStrings.darkMode: 60 | context.read().setThemeModeSilent = ThemeMode.dark; 61 | 62 | case AppStrings.lightMode: 63 | context.read().setThemeModeSilent = ThemeMode.light; 64 | 65 | case AppStrings.systemMode: 66 | context.read().setThemeModeSilent = ThemeMode.system; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:device_preview/device_preview.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | import 'package:task_manager/app/app.dart'; 5 | 6 | main() async { 7 | WidgetsFlutterBinding.ensureInitialized(); 8 | SharedPreferences preferences = await SharedPreferences.getInstance(); 9 | String? theme = preferences.getString("themeMode"); 10 | theme ??= "system"; 11 | runApp( 12 | DevicePreview( 13 | builder: (_) => TaskManager( 14 | userTheme: theme!, 15 | ), 16 | ), 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /lib/models/loginModels/login_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:task_manager/models/loginModels/user_data.dart'; 2 | 3 | class LoginModel { 4 | String? status; 5 | String? token; 6 | UserData? data; 7 | 8 | LoginModel({this.status, this.token, this.data}); 9 | 10 | LoginModel.fromJson(Map json) { 11 | status = json['status']; 12 | token = json['token']; 13 | data = json['data'] != null ? UserData.fromJson(json['data']) : null; 14 | } 15 | 16 | Map toJson() { 17 | final Map data = {}; 18 | data['status'] = status; 19 | data['token'] = token; 20 | if (this.data != null) { 21 | data['data'] = this.data!.toJson(); 22 | } 23 | return data; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/models/loginModels/user_data.dart: -------------------------------------------------------------------------------- 1 | class UserData { 2 | String? email, firstName, lastName, mobile, password, photo; 3 | 4 | UserData({ 5 | required this.email, 6 | required this.firstName, 7 | required this.lastName, 8 | required this.mobile, 9 | required this.password, 10 | this.photo = "", 11 | }); 12 | 13 | UserData.fromJson(Map json) { 14 | email = json['email']; 15 | firstName = json['firstName']; 16 | lastName = json['lastName']; 17 | mobile = json['mobile']; 18 | photo = json['photo']; 19 | password = json['password']; 20 | } 21 | 22 | Map toJson() { 23 | final Map data = {}; 24 | data['email'] = email; 25 | data['firstName'] = firstName; 26 | data['lastName'] = lastName; 27 | data['mobile'] = mobile; 28 | data['password'] = password; 29 | data['photo'] = photo; 30 | return data; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/models/responseModel/failure.dart: -------------------------------------------------------------------------------- 1 | class Failure { 2 | int statusCode; 3 | String message; 4 | 5 | Failure(this.statusCode, this.message); 6 | } 7 | -------------------------------------------------------------------------------- /lib/models/responseModel/response_code.dart: -------------------------------------------------------------------------------- 1 | import 'package:task_manager/utils/app_strings.dart'; 2 | 3 | class ResponseCode { 4 | static Map httpStatusMessages = { 5 | // Informational Responses 6 | 200: "Successful request", 7 | 400: 8 | "The request could not be understood by the server due to malformed syntax", 9 | 401: AppStrings.signInFailureMessage, 10 | 403: "The server understood the request but refused to fulfill it", 11 | 404: "The requested resource could not be found", 12 | 500: 13 | "A generic error message, given when an unexpected condition was encountered and no more specific message is suitable", 14 | 503: 15 | "The server is currently unable to handle the request due to temporary overloading or maintenance of the server", 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /lib/models/responseModel/success.dart: -------------------------------------------------------------------------------- 1 | class Success { 2 | Object? response; 3 | 4 | Success({this.response}); 5 | } 6 | -------------------------------------------------------------------------------- /lib/models/taskListModel/task_data.dart: -------------------------------------------------------------------------------- 1 | class TaskData { 2 | String? sId; 3 | String? title; 4 | String? description; 5 | String? status; 6 | String? createdDate; 7 | bool isTileExpanded = false; 8 | 9 | TaskData( 10 | {this.sId, this.title, this.description, this.status, this.createdDate}); 11 | 12 | TaskData.fromJson(Map json) { 13 | sId = json['_id']; 14 | title = json['title']; 15 | description = json['description']; 16 | status = json['status']; 17 | createdDate = json['createdDate']; 18 | } 19 | 20 | Map toJson() { 21 | final Map data = {}; 22 | data['_id'] = sId; 23 | data['title'] = title; 24 | data['description'] = description; 25 | data['status'] = status; 26 | data['createdDate'] = createdDate; 27 | return data; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/models/taskListModel/task_list_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:task_manager/models/taskListModel/task_data.dart'; 2 | 3 | class TaskListModel { 4 | String? status; 5 | List? taskData; 6 | 7 | TaskListModel({this.status, this.taskData}); 8 | 9 | TaskListModel.fromJson(Map json) { 10 | status = json['status']; 11 | if (json['data'] != null) { 12 | taskData = []; 13 | json['data'].forEach((v) { 14 | taskData!.add(TaskData.fromJson(v)); 15 | }); 16 | } 17 | } 18 | 19 | Map toJson() { 20 | final Map data = {}; 21 | data['status'] = status; 22 | if (taskData != null) { 23 | data['data'] = taskData!.map((v) => v.toJson()).toList(); 24 | } 25 | return data; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/models/taskStatusCountModels/status_data.dart: -------------------------------------------------------------------------------- 1 | class StatusData { 2 | String? sId; 3 | int? sum; 4 | 5 | StatusData({this.sId, this.sum}); 6 | 7 | StatusData.fromJson(Map json) { 8 | sId = json['_id']; 9 | sum = json['sum']; 10 | } 11 | 12 | Map toJson() { 13 | final Map data = {}; 14 | data['_id'] = sId; 15 | data['sum'] = sum; 16 | return data; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/models/taskStatusCountModels/task_status_count_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:task_manager/models/taskStatusCountModels/status_data.dart'; 2 | 3 | class TaskStatusCountModel { 4 | String? status; 5 | List? statusData; 6 | 7 | TaskStatusCountModel({this.status, this.statusData}); 8 | 9 | TaskStatusCountModel.fromJson(Map json) { 10 | status = json['status']; 11 | if (json['data'] != null) { 12 | statusData = []; 13 | json['data'].forEach((v) { 14 | statusData!.add(StatusData.fromJson(v)); 15 | }); 16 | } 17 | } 18 | 19 | Map toJson() { 20 | final Map data = {}; 21 | data['status'] = status; 22 | if (statusData != null) { 23 | data['data'] = statusData!.map((v) => v.toJson()).toList(); 24 | } 25 | return data; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/services/auth_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:task_manager/models/loginModels/user_data.dart'; 2 | import 'package:task_manager/services/network_request.dart'; 3 | import 'package:task_manager/utils/app_strings.dart'; 4 | 5 | class AuthService { 6 | Future registration(UserData userData) async { 7 | return await NetworkRequest().postRequest( 8 | uri: "${AppStrings.baseUrl}${AppStrings.registrationEndpoint}", 9 | body: userData.toJson(), 10 | headers: {"content-type": "application/json"}, 11 | ); 12 | } 13 | 14 | Future signIn(String email, String password) async { 15 | Map signInCredentials = { 16 | "email": email, 17 | "password": password, 18 | }; 19 | return await NetworkRequest().postRequest( 20 | uri: "${AppStrings.baseUrl}${AppStrings.signInEndpoint}", 21 | body: signInCredentials, 22 | headers: {"content-type": "application/json"}, 23 | shouldAuthenticateToken: false); 24 | } 25 | 26 | Future requestOTP(String email) async { 27 | return await NetworkRequest().getRequest( 28 | uri: "${AppStrings.baseUrl}${AppStrings.recoverEmailEndpoint}/$email", 29 | ); 30 | } 31 | 32 | Future verifyOTP(String otp, String email) async { 33 | return await NetworkRequest().getRequest( 34 | uri: "${AppStrings.baseUrl}${AppStrings.verifyOTPEndpoint}/$email/$otp", 35 | ); 36 | } 37 | 38 | Future resetPassword(Map data) async { 39 | return await NetworkRequest().postRequest( 40 | uri: "${AppStrings.baseUrl}${AppStrings.resetPasswordEndpoint}", 41 | body: data, 42 | headers: {"content-type": "application/json"}, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/services/connectivity_checker.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:internet_connection_checker/internet_connection_checker.dart'; 5 | 6 | class ConnectivityChecker extends ChangeNotifier { 7 | bool _isDeviceConnected = true; 8 | late StreamSubscription subscription; 9 | bool _isDisposed = false; 10 | bool get isDeviceConnected => _isDeviceConnected; 11 | 12 | Future initConnectivityChecker() async { 13 | subscription = InternetConnectionChecker().onStatusChange.listen( 14 | (InternetConnectionStatus status) { 15 | switch (status) { 16 | case InternetConnectionStatus.connected: 17 | _isDeviceConnected = true; 18 | notifyListeners(); 19 | break; 20 | case InternetConnectionStatus.disconnected: 21 | _isDeviceConnected = false; 22 | notifyListeners(); 23 | break; 24 | } 25 | }, 26 | ); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | _isDisposed = true; // Set the disposal flag 32 | subscription.cancel(); // Cancel the subscription 33 | super.dispose(); // Call the superclass dispose method 34 | } 35 | 36 | Future disableInternetConnectionChecker() async { 37 | if (_isDisposed) return; // Check if the instance is disposed 38 | await subscription.cancel(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/services/network_request.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:http/http.dart'; 5 | import 'package:task_manager/models/responseModel/failure.dart'; 6 | import 'package:task_manager/models/responseModel/success.dart'; 7 | import 'package:task_manager/utils/app_navigation.dart'; 8 | 9 | import '../models/responseModel/response_code.dart'; 10 | import '../utils/app_strings.dart'; 11 | 12 | class NetworkRequest { 13 | Object? finalResponse; 14 | static NetworkRequest? instance; 15 | 16 | NetworkRequest._(); 17 | 18 | factory NetworkRequest() { 19 | return instance ??= NetworkRequest._(); 20 | } 21 | 22 | Future getRequest( 23 | {required String uri, 24 | Map? headers, 25 | bool shouldAuthenticateToken = true}) async { 26 | try { 27 | Response response = await get(Uri.parse(uri), headers: headers); 28 | finalResponse = getResponse(response, 29 | shouldAuthenticateToken: shouldAuthenticateToken); 30 | } on ClientException { 31 | finalResponse = Failure(600, AppStrings.unknownResponseText); 32 | } catch (exception) { 33 | if (kDebugMode) { 34 | debugPrint(exception.toString()); 35 | } 36 | finalResponse = Failure(600, AppStrings.unknownResponseText); 37 | } 38 | return finalResponse!; 39 | } 40 | 41 | Future postRequest( 42 | {required String uri, 43 | Map? headers, 44 | required Map body, 45 | bool shouldAuthenticateToken = true}) async { 46 | try { 47 | Response response = 48 | await post(Uri.parse(uri), headers: headers, body: jsonEncode(body)); 49 | finalResponse = getResponse(response, 50 | shouldAuthenticateToken: shouldAuthenticateToken); 51 | } on ClientException { 52 | finalResponse = Failure(600, AppStrings.unknownResponseText); 53 | } catch (exception) { 54 | if (kDebugMode) { 55 | debugPrint(exception.toString()); 56 | } 57 | finalResponse = Failure(600, AppStrings.unknownResponseText); 58 | } 59 | return finalResponse!; 60 | } 61 | 62 | Object getResponse(Response response, {bool shouldAuthenticateToken = true}) { 63 | if (response.statusCode == 200) { 64 | final jsonData = jsonDecode(response.body); 65 | return Success(response: jsonData); 66 | } else if (shouldAuthenticateToken && response.statusCode == 401) { 67 | AppNavigation().signOutUser(); 68 | return Failure(401, AppStrings.sessionExpiredText); 69 | } else { 70 | return Failure( 71 | response.statusCode, 72 | ResponseCode.httpStatusMessages[response.statusCode] ?? 73 | AppStrings.unknownResponseText); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/services/task_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:task_manager/services/network_request.dart'; 2 | import 'package:task_manager/utils/app_strings.dart'; 3 | import 'package:task_manager/viewModels/auth_view_model.dart'; 4 | 5 | class TaskService extends AuthViewModel { 6 | Future fetchTaskStatusCount(String token) async { 7 | return await NetworkRequest().getRequest( 8 | uri: "${AppStrings.baseUrl}${AppStrings.taskStatusCountEndpoint}", 9 | headers: {"token": token}, 10 | ); 11 | } 12 | 13 | Future fetchTaskList(String taskStatus, String token) async { 14 | return await NetworkRequest().getRequest( 15 | uri: 16 | "${AppStrings.baseUrl}${AppStrings.listTaskByStatusEndpoint}/$taskStatus", 17 | headers: {"token": token}, 18 | ); 19 | } 20 | 21 | Future createTask(String token, Map taskData) async { 22 | return await NetworkRequest().postRequest( 23 | uri: "${AppStrings.baseUrl}${AppStrings.createTaskEndpoint}", 24 | body: taskData, 25 | headers: {"content-type": "application/json", "token": token}, 26 | ); 27 | } 28 | 29 | Future updateTask( 30 | String token, String taskId, String taskStatus) async { 31 | return await NetworkRequest().getRequest( 32 | uri: 33 | "${AppStrings.baseUrl}${AppStrings.updateTaskEndpoint}/$taskId/$taskStatus", 34 | headers: {"token": token}, 35 | ); 36 | } 37 | 38 | Future deleteTask(String taskId, String token) async { 39 | return await NetworkRequest().getRequest( 40 | uri: "${AppStrings.baseUrl}${AppStrings.deleteTaskEndpoint}/$taskId", 41 | headers: {"token": token}, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/services/user_info_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:task_manager/models/loginModels/user_data.dart'; 2 | import 'package:task_manager/services/network_request.dart'; 3 | import 'package:task_manager/utils/app_strings.dart'; 4 | 5 | class UserInfoService { 6 | static late Object finalResponse; 7 | 8 | static Future updateUserProfile( 9 | String token, UserData userData) async { 10 | return await NetworkRequest().postRequest( 11 | uri: "${AppStrings.baseUrl}${AppStrings.profileUpdateEndpoint}", 12 | body: userData.toJson(), 13 | headers: {"content-type": "application/json", "token": token}, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/themes/app_elevated_button_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/utils/app_color.dart'; 3 | 4 | class AppElevatedButtonStyle { 5 | static ElevatedButtonThemeData getElevatedButtonStyle() => 6 | ElevatedButtonThemeData( 7 | style: ElevatedButton.styleFrom( 8 | minimumSize: const Size(double.infinity, 50), 9 | elevation: 0, 10 | backgroundColor: AppColor.appPrimaryColor, 11 | foregroundColor: AppColor.elevatedButtonForegroundColor, 12 | shape: RoundedRectangleBorder( 13 | borderRadius: BorderRadius.circular(8), 14 | ), 15 | ), 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/themes/app_text_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../utils/app_color.dart'; 4 | 5 | class AppTextStyle { 6 | static TextTheme getTextStyleLight() => TextTheme( 7 | headlineLarge: getDefaultTextStyle().copyWith( 8 | fontSize: 28, 9 | color: AppColor.headLineTextLargeColorLight, 10 | fontFamily: "Poppins Bold"), 11 | titleLarge: getDefaultTextStyle().copyWith( 12 | fontSize: 20, 13 | color: AppColor.headLineTextLargeColorLight, 14 | ), 15 | titleMedium: getDefaultTextStyle().copyWith( 16 | fontSize: 15, 17 | color: AppColor.headLineTextLargeColorLight, 18 | ), 19 | titleSmall: getDefaultTextStyle().copyWith( 20 | fontSize: 10, 21 | color: AppColor.headLineTextLargeColorLight, 22 | ), 23 | bodySmall: getDefaultTextStyle().copyWith( 24 | fontSize: 11, 25 | color: AppColor.bodySmallTextColor, 26 | ), 27 | bodyMedium: getDefaultTextStyle(), 28 | labelMedium: getDefaultTextStyle().copyWith( 29 | fontSize: 14.5, 30 | color: AppColor.labelSmallTextColor, 31 | ), 32 | labelSmall: getDefaultTextStyle().copyWith( 33 | fontSize: 9, 34 | color: AppColor.labelSmallTextColor, 35 | ), 36 | ); 37 | 38 | static TextTheme getTextStyleDark() => getTextStyleLight().copyWith( 39 | titleMedium: getDefaultTextStyle().copyWith( 40 | fontSize: 15, 41 | color: AppColor.headLineTextLargeColorDark, 42 | ), 43 | titleLarge: getDefaultTextStyle().copyWith( 44 | fontSize: 20, 45 | color: AppColor.headLineTextLargeColorDark, 46 | ), 47 | titleSmall: getDefaultTextStyle().copyWith( 48 | fontSize: 10, 49 | color: AppColor.headLineTextLargeColorDark, 50 | ), 51 | headlineLarge: getDefaultTextStyle().copyWith( 52 | fontSize: 28, 53 | color: AppColor.headLineTextLargeColorDark, 54 | fontFamily: "Poppins Bold"), 55 | bodyMedium: getDefaultTextStyle() 56 | .copyWith(color: AppColor.bodyMediumTextColorDark)); 57 | 58 | static TextStyle getDefaultTextStyle() => const TextStyle( 59 | fontSize: 13, 60 | color: AppColor.bodyMediumTextColorLight, 61 | fontFamily: "Poppins", 62 | fontWeight: FontWeight.bold, 63 | letterSpacing: 0.5); 64 | } 65 | -------------------------------------------------------------------------------- /lib/themes/app_textfield_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/utils/app_color.dart'; 3 | 4 | class AppTextFieldStyle { 5 | static InputDecorationTheme getTextFieldThemeLight() => InputDecorationTheme( 6 | fillColor: Colors.white, 7 | filled: true, 8 | focusColor: AppColor.appPrimaryColor, 9 | floatingLabelStyle: 10 | const TextStyle(color: AppColor.appPrimaryColor, fontSize: 14), 11 | contentPadding: const EdgeInsets.all(8), 12 | hintStyle: TextStyle( 13 | color: AppColor.textFieldHintColor, 14 | fontSize: 13, 15 | fontWeight: FontWeight.normal, 16 | fontFamily: "Poppins"), 17 | enabledBorder: OutlineInputBorder( 18 | borderSide: BorderSide.none, 19 | borderRadius: BorderRadius.circular(5)), 20 | focusedBorder: const OutlineInputBorder( 21 | borderSide: BorderSide(color: AppColor.appPrimaryColor, width: 1.5), 22 | ), 23 | errorBorder: const OutlineInputBorder( 24 | borderSide: BorderSide(color: Colors.red, width: 2), 25 | ), 26 | focusedErrorBorder: const OutlineInputBorder( 27 | borderSide: BorderSide(color: Colors.red, width: 2), 28 | ), 29 | ); 30 | 31 | static InputDecorationTheme getTextFieldThemeDark() => 32 | getTextFieldThemeLight().copyWith( 33 | fillColor: AppColor.textFieldColorDark, 34 | enabledBorder: const OutlineInputBorder( 35 | borderSide: BorderSide(color: Colors.grey, width: 1))); 36 | } 37 | -------------------------------------------------------------------------------- /lib/themes/app_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/themes/card_style.dart'; 3 | import 'package:task_manager/themes/expansion_tile_style.dart'; 4 | import 'package:task_manager/themes/popup_menu_style.dart'; 5 | 6 | import '../utils/app_color.dart'; 7 | import 'app_elevated_button_style.dart'; 8 | import 'app_text_style.dart'; 9 | import 'app_textfield_style.dart'; 10 | import 'appbar_style.dart'; 11 | 12 | class AppTheme { 13 | static ThemeData getLightTheme() => ThemeData( 14 | useMaterial3: false, 15 | scaffoldBackgroundColor: AppColor.scaffoldBackgroundColorLight, 16 | textTheme: AppTextStyle.getTextStyleLight(), 17 | inputDecorationTheme: AppTextFieldStyle.getTextFieldThemeLight(), 18 | elevatedButtonTheme: AppElevatedButtonStyle.getElevatedButtonStyle(), 19 | appBarTheme: AppbarStyle.getLightAppbarStyle(), 20 | cardTheme: CardStyle.getCardStyleLight(), 21 | popupMenuTheme: PopupMenuStyle.getPopupMenuLight(), 22 | expansionTileTheme: ExpansionTileStyle.getExpansionStyleLight(), 23 | ); 24 | 25 | static ThemeData getDarkTheme() => getLightTheme().copyWith( 26 | scaffoldBackgroundColor: AppColor.scaffoldBackgroundColorDark, 27 | appBarTheme: AppbarStyle.getDarkAppbarStyle(), 28 | cardTheme: CardStyle.getCardStyleDark(), 29 | textTheme: AppTextStyle.getTextStyleDark(), 30 | inputDecorationTheme: AppTextFieldStyle.getTextFieldThemeDark(), 31 | popupMenuTheme: PopupMenuStyle.getPopupMenuDark(), 32 | expansionTileTheme: ExpansionTileStyle.getExpansionStyleDark(), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/themes/appbar_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/utils/app_color.dart'; 3 | 4 | class AppbarStyle { 5 | static AppBarTheme getLightAppbarStyle() => const AppBarTheme( 6 | backgroundColor: AppColor.appBarBackgroundColorLight, 7 | foregroundColor: AppColor.appBarForegroundColor, 8 | surfaceTintColor: AppColor.appBarBackgroundColorLight, 9 | elevation: 0, 10 | actionsIconTheme: IconThemeData(size: 27)); 11 | 12 | static AppBarTheme getDarkAppbarStyle() => getLightAppbarStyle() 13 | .copyWith(backgroundColor: AppColor.appBarBackgroundColorDark); 14 | } 15 | -------------------------------------------------------------------------------- /lib/themes/card_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/utils/app_color.dart'; 3 | 4 | class CardStyle { 5 | static CardTheme getCardStyleLight() => CardTheme( 6 | elevation: 0, 7 | color: AppColor.cardBackgroundColorLight, 8 | shape: RoundedRectangleBorder( 9 | borderRadius: BorderRadius.circular(5), 10 | ), 11 | ); 12 | 13 | static CardTheme getCardStyleDark() => 14 | getCardStyleLight().copyWith(color: AppColor.cardBackgroundColorDark); 15 | } 16 | -------------------------------------------------------------------------------- /lib/themes/expansion_tile_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/utils/app_color.dart'; 3 | 4 | class ExpansionTileStyle { 5 | static ExpansionTileThemeData getExpansionStyleLight() => 6 | ExpansionTileThemeData( 7 | tilePadding: const EdgeInsets.symmetric(horizontal: 0), 8 | expansionAnimationStyle: AnimationStyle( 9 | curve: Curves.linearToEaseOut, 10 | duration: const Duration(milliseconds: 700), 11 | ), 12 | collapsedIconColor: AppColor.expansionTileCollapsedIconColor, 13 | childrenPadding: const EdgeInsets.only(bottom: 5), 14 | shape: const Border(), 15 | ); 16 | 17 | static ExpansionTileThemeData getExpansionStyleDark() => 18 | getExpansionStyleLight(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/themes/navigation_bar_style.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/themes/popup_menu_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/utils/app_color.dart'; 3 | 4 | class PopupMenuStyle { 5 | static PopupMenuThemeData getPopupMenuLight() => PopupMenuThemeData( 6 | color: AppColor.popupMenuColorLight, 7 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), 8 | elevation: 4, 9 | ); 10 | 11 | static PopupMenuThemeData getPopupMenuDark() => getPopupMenuLight().copyWith( 12 | color: AppColor.popupMenuColorDark, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /lib/themes/theme_changer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/utils/app_assets.dart'; 3 | import 'package:task_manager/utils/app_color.dart'; 4 | 5 | class ThemeChanger extends ChangeNotifier { 6 | ThemeMode _themeMode = ThemeMode.system; 7 | bool _isAppLaunched = false; 8 | 9 | ThemeMode get themeMode => _themeMode; 10 | 11 | bool get isAppLaunched => _isAppLaunched; 12 | 13 | set setIsAppLaunched(bool isAppLaunched) { 14 | _isAppLaunched = isAppLaunched; 15 | } 16 | 17 | set setThemeMode(ThemeMode mode) { 18 | _themeMode = mode; 19 | notifyListeners(); 20 | } 21 | 22 | set setThemeModeSilent(ThemeMode mode) { 23 | _themeMode = mode; 24 | } 25 | 26 | String getBackgroundImage(BuildContext context) { 27 | if (getThemeMode(context) == ThemeMode.dark) { 28 | return AppAssets.backgroundImageDark; 29 | } 30 | return AppAssets.backgroundImageLight; 31 | } 32 | 33 | ThemeMode getThemeMode(BuildContext context) { 34 | if (_themeMode == ThemeMode.system && 35 | MediaQuery.of(context).platformBrightness == Brightness.dark) { 36 | return ThemeMode.dark; 37 | } 38 | if (_themeMode == ThemeMode.dark) { 39 | return ThemeMode.dark; 40 | } 41 | return ThemeMode.light; 42 | } 43 | 44 | Color getContainerColor(BuildContext context) { 45 | if (MediaQuery.of(context).platformBrightness == Brightness.dark) { 46 | return AppColor.darkComponentsColor; 47 | } 48 | return Colors.white; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/utils/app_assets.dart: -------------------------------------------------------------------------------- 1 | class AppAssets { 2 | static const String basePath = "assets"; 3 | static const String imagePath = "$basePath/images"; 4 | static const String backgroundImageLight = "$imagePath/background.svg"; 5 | static const String backgroundImageDark = "$imagePath/darkBackground.svg"; 6 | static const String logo = "$imagePath/logo.svg"; 7 | static const String userDefaultImage = "$imagePath/user.png"; 8 | static const String emptyList = "$imagePath/empty.svg"; 9 | static const String noInternet = "$imagePath/noInternet.svg"; 10 | } 11 | -------------------------------------------------------------------------------- /lib/utils/app_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColor { 4 | static const Color appPrimaryColor = Color(0xFF21BF73); 5 | static const Color darkComponentsColor = Color(0xFF222222); 6 | static const Color scaffoldBackgroundColorLight = Color(0xFFFAF8F6); 7 | static const Color scaffoldBackgroundColorDark = Color(0xFF282828); 8 | static const Color headLineTextLargeColorLight = Colors.black; 9 | static const Color headLineTextLargeColorDark = Colors.white; 10 | static const Color bodySmallTextColor = Colors.grey; 11 | static const Color bodyMediumTextColorLight = Colors.black; 12 | static const Color bodyMediumTextColorDark = Colors.white; 13 | static const Color labelSmallTextColor = Colors.white; 14 | static Color textFieldHintColor = Colors.grey.shade400; 15 | static const Color elevatedButtonForegroundColor = Colors.white; 16 | static const Color circularProgressbarColor = Colors.white; 17 | static const Color snackBarSuccessColor = appPrimaryColor; 18 | static Color snackBarFailureColor = Colors.red.shade300; 19 | static Color snackBarWarningColor = const Color(0xFFFFCC00); 20 | static const Color appBarForegroundColor = Colors.white; 21 | static const Color appBarBackgroundColorLight = appPrimaryColor; 22 | static const Color appBarBackgroundColorDark = darkComponentsColor; 23 | static const Color bottomBarUnselectedColor = Colors.black; 24 | static const Color bottomBarSelectedColor = appPrimaryColor; 25 | static const Color bottomBarBackgroundColor = scaffoldBackgroundColorLight; 26 | static Color newTaskChipColor = const Color(0xFF17C1E8); 27 | static const Color completedChipColor = appPrimaryColor; 28 | static Color progressChipColor = const Color(0xFFCB0C9F); 29 | static Color canceledChipColor = const Color(0xFFF15056); 30 | static const Color newTaskChipTextColor = Colors.white; 31 | static const Color deleteIconColor = Colors.red; 32 | static const Color editIconColor = appPrimaryColor; 33 | static const Color photoPickerColor = Colors.grey; 34 | static const Color cardBackgroundColorLight = Colors.white; 35 | static const Color cardBackgroundColorDark = darkComponentsColor; 36 | static const Color textFieldColorDark = darkComponentsColor; 37 | static const Color popupMenuColorLight = Colors.white; 38 | static const Color popupMenuColorDark = darkComponentsColor; 39 | static const Color expansionTileCollapsedIconColor = Colors.grey; 40 | } 41 | -------------------------------------------------------------------------------- /lib/utils/app_navigation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | import '../app/app.dart'; 5 | import '../viewModels/auth_view_model.dart'; 6 | import 'app_routes.dart'; 7 | 8 | class AppNavigation extends AuthViewModel { 9 | static AppNavigation? _instance; 10 | 11 | AppNavigation._(); 12 | 13 | factory AppNavigation() { 14 | //singleton pattern 15 | _instance ??= AppNavigation._(); 16 | return _instance!; 17 | } 18 | 19 | void gotoSignIn() { 20 | setPasswordObscure = true; 21 | Navigator.pop(TaskManager.navigatorKey.currentContext!); 22 | } 23 | 24 | void gotoSignUp(FocusNode emailFocusNode, FocusNode passwordFocusNode) { 25 | setPasswordObscure = true; 26 | Navigator.pushNamed( 27 | TaskManager.navigatorKey.currentContext!, AppRoutes.signUpScreen) 28 | .then((value) { 29 | emailFocusNode.unfocus(); 30 | passwordFocusNode.unfocus(); 31 | }); 32 | } 33 | 34 | Future signOutUser() async { 35 | SharedPreferences preferences = await SharedPreferences.getInstance(); 36 | preferences.clear(); 37 | Navigator.pushNamedAndRemoveUntil(TaskManager.navigatorKey.currentContext!, 38 | AppRoutes.signInScreen, (route) => false); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/utils/app_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/views/dashboardScreen/dashboard_screen.dart'; 3 | import 'package:task_manager/views/newTaskAddScreen/add_task_screen.dart'; 4 | import 'package:task_manager/views/splashScreen/splash_screen.dart'; 5 | import 'package:task_manager/views/updateProfileScreen/update_profile_screen.dart'; 6 | 7 | import '../views/authScreens/forgetPasswordScreen/emailVerificationScreen/email_verification_screen.dart'; 8 | import '../views/authScreens/forgetPasswordScreen/pinVerificationScreen/pin_verification_screen.dart'; 9 | import '../views/authScreens/forgetPasswordScreen/setPasswordScreen/set_password_screen.dart'; 10 | import '../views/authScreens/signInScreen/sign_in_screen.dart'; 11 | import '../views/authScreens/signUpScreen/sign_up_screen.dart'; 12 | 13 | class AppRoutes { 14 | static const splashScreen = "/splashScreen"; 15 | static const signInScreen = "/signInScreen"; 16 | static const signUpScreen = "/signUpScreen"; 17 | static const dashboardScreen = "/dashboardScreen"; 18 | static const emailVerificationScreen = "/emailVerificationScreen"; 19 | static const pinVerificationScreen = "/pinVerificationScreen"; 20 | static const setPasswordScreen = "/setPasswordScreen"; 21 | static const addTaskScreen = "/addTaskScreen"; 22 | static const updateProfileScreen = "/updateProfileScreen"; 23 | 24 | static MaterialPageRoute? generateRoute(RouteSettings routeSettings) { 25 | final Map routes = { 26 | splashScreen: (context) => const SplashScreen(), 27 | signInScreen: (context) => const SignInScreen(), 28 | signUpScreen: (context) => const SignUpScreen(), 29 | dashboardScreen: (context) => const DashboardScreen(), 30 | emailVerificationScreen: (context) => const EmailVerificationScreen(), 31 | pinVerificationScreen: (context) => const PinVerificationScreen(), 32 | setPasswordScreen: (context) => const SetPasswordScreen(), 33 | addTaskScreen: (context) => const AddTaskScreen(), 34 | updateProfileScreen: (context) => const UpdateProfileScreen(), 35 | }; 36 | final WidgetBuilder? builder = routes[routeSettings.name]; 37 | return (builder != null) ? MaterialPageRoute(builder: builder) : null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/utils/app_strings.dart: -------------------------------------------------------------------------------- 1 | class AppStrings { 2 | //api constants 3 | static const String baseUrl = "https://task.teamrabbil.com/api/v1/"; 4 | static const String registrationEndpoint = "registration"; 5 | static const String signInEndpoint = "login"; 6 | static const String recoverEmailEndpoint = "RecoverVerifyEmail"; 7 | static const String verifyOTPEndpoint = "RecoverVerifyOTP"; 8 | static const String resetPasswordEndpoint = "RecoverResetPass"; 9 | static const String taskStatusCountEndpoint = "taskStatusCount"; 10 | static const String listTaskByStatusEndpoint = "listTaskByStatus"; 11 | static const String deleteTaskEndpoint = "deleteTask"; 12 | static const String createTaskEndpoint = "createTask"; 13 | static const String updateTaskEndpoint = "updateTaskStatus"; 14 | static const String profileUpdateEndpoint = "profileUpdate"; 15 | static const String unknownResponseText = "Unknown Error"; 16 | static const String serverConnectionErrorText = "Server Connection error"; 17 | 18 | //regularExpression 19 | static const String nameRegEX = r'^[a-z A-Z]+$'; 20 | static const String digitsRegEx = r'^[0-9]+$'; 21 | static const String phoneNumberRegEx = r'^(?:\+?88|0088)?01[3-9]\d{8}$'; 22 | static const String emailRegEx = 23 | r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'; 24 | 25 | //bottomNavigationBar 26 | static const String bottomBarProgress = "Progress"; 27 | static const String bottomBarCompleted = "Completed"; 28 | static const String bottomBarCanceled = "Canceled"; 29 | static const String bottomBarAdd = "New Task"; 30 | 31 | static const String emailTextFieldHint = "Email"; 32 | static const String passwordTextFieldHint = "Password"; 33 | static const String firstNameTextFieldHint = "First Name"; 34 | static const String lastNameTextFieldHint = "Last Name"; 35 | static const String confirmPassTextFieldHint = "Confirm Password"; 36 | static const String mobileNumberTextFieldHint = "Mobile"; 37 | static const String signInHeaderText = "Get Started With"; 38 | static const String obscuringChar = "*"; 39 | static const String forgetPasswordText = "Forget Password?"; 40 | static const String signInBottomTextOne = "Don't have account?"; 41 | static const String signInBottomTextTwo = " Sign up"; 42 | static const String signUpBottomTextOne = "Have account? "; 43 | static const String signUpBottomTextTwo = "Sign in"; 44 | static const String signUpHeaderText = "Join With Us"; 45 | static const String emailErrorText = "Invalid email address"; 46 | static const String passwordErrorText = "Invalid password"; 47 | static const String passwordLengthErrorText = 48 | "Please give at least 8 characters"; 49 | static const String confirmPasswordErrorText = "Password didn't match"; 50 | static const String firstNameErrorText = "Invalid first name"; 51 | static const String lastNameErrorText = "Invalid last name"; 52 | static const String mobileNumberErrorText = "Invalid mobile number"; 53 | static const String emailVerificationHeaderText = "Your Email Address"; 54 | static const String emailVerificationBodyText = 55 | "A 6 digit verification pin will send to your email address"; 56 | static const String pinVerificationHeaderText = "Pin Verification"; 57 | static const String pinVerificationBodyText = 58 | "A 6 digit verification pin will send to your email address"; 59 | static const String pinVerificationButtonText = "Verify"; 60 | static const String setPasswordHeaderText = "Set Password"; 61 | static const String setPasswordBodyText = 62 | "Minimum length password 8 character with letter and number combination"; 63 | static const String setPasswordButtonText = "Confirm"; 64 | 65 | //registration SnackBar 66 | static const String signInFailureTitle = "Login Failed"; 67 | static const String signInFailureMessage = "Please use correct credentials"; 68 | static const String registrationSuccessTitle = "Success!"; 69 | static const String registrationFailureTitle = "Oops!"; 70 | static const String registrationSuccessMessage = 71 | "You have been registered successfully"; 72 | static const String registrationFailureMessage = 73 | "Something went wrong, try again later"; 74 | static const String sendOTPFailureTitle = "Failed!"; 75 | static const String sendOTPFailureMessage = "No user found with this email!"; 76 | static const String emptyPinVerificationFieldTitle = "Invalid Pin"; 77 | static const String wrongPinVerificationFieldTitle = "Wrong Pin"; 78 | static const String emptyPinVerificationFieldMessage = 79 | "Please insert all fields"; 80 | static const String wrongPinVerificationFieldMessage = 81 | "Enter correct pin and try again"; 82 | static const String resetPasswordFailureTitle = "Failed!"; 83 | static const String resetPasswordSuccessTitle = "Success!"; 84 | static const String resetPasswordSuccessMessage = 85 | "Your password has been reset successfully"; 86 | static const String taskItemDeleteSuccessTitle = "Success!"; 87 | static const String taskItemDeleteFailedTitle = "Failed!"; 88 | static const String taskItemDeleteSuccessMessage = 89 | "Task has been deleted successfully"; 90 | static const String taskItemDeleteFailureMessage = 91 | "Item failed to delete, try again"; 92 | static const String newTaskAddSuccessTitle = "Success!"; 93 | static const String newTaskAddFailureTitle = "Failed!"; 94 | static const String newTaskAddSuccessMessage = 95 | "New Task has been added successfully"; 96 | static const String newTaskAddFailureMessage = 97 | "Failed to add new task, something went wrong!"; 98 | 99 | //noData 100 | static const String noNewTaskData = "Your new task list is currently empty"; 101 | static const String noCompletedTaskData = 102 | "You haven't completed any task yet"; 103 | static const String noProgressTaskData = 104 | "There is no on going task for you now"; 105 | static const String noNewCanceledData = 106 | "Your canceled task list is currently empty"; 107 | static const String taskStatusUpdateSuccessTitle = "Success!"; 108 | static const String taskStatusUpdateFailureTitle = "Failed!"; 109 | static const String taskStatusUpdateSuccessMessage = 110 | "Your task status has been updated successfully"; 111 | static const String taskStatusUpdateFailureMessage = 112 | "Something went wrong please try again later"; 113 | 114 | static const String addTaskScreenTitle = "Add New Task"; 115 | static const String subjectTextFieldHint = "Subject"; 116 | static const String descriptionTextFieldHint = "Description"; 117 | static const String updateProfileScreenTitle = "Update Profile"; 118 | static const String photoPickerText = "Photos"; 119 | static const String updateUserProfileSuccessTitle = "Success!"; 120 | static const String updateUserProfileFailureTitle = "Failed!"; 121 | static const String updateUserProfileSuccessMessage = 122 | "Your profile information has been updated."; 123 | static const String sessionExpiredText = 124 | "Session expired please login again."; 125 | static const String taskStatusNew = "New"; 126 | static const String taskStatusProgress = "Progress"; 127 | static const String taskStatusCompleted = "Completed"; 128 | static const String taskStatusCanceled = "Canceled"; 129 | static const String chooseImageFileText = "Choose Image"; 130 | static const String lightMode = "light"; 131 | static const String darkMode = "dark"; 132 | static const String systemMode = "system"; 133 | static const String noInternetText = 134 | "Oops! Seems you don't have internet connection"; 135 | } 136 | -------------------------------------------------------------------------------- /lib/viewModels/auth_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:jwt_decoder/jwt_decoder.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | import 'package:task_manager/models/loginModels/login_model.dart'; 6 | import 'package:task_manager/models/responseModel/success.dart'; 7 | import 'package:task_manager/services/auth_service.dart'; 8 | import 'package:task_manager/utils/app_navigation.dart'; 9 | import 'package:task_manager/viewModels/user_view_model.dart'; 10 | 11 | import '../models/loginModels/user_data.dart'; 12 | 13 | class AuthViewModel extends ChangeNotifier { 14 | bool _isPasswordObscured = true; 15 | bool _isLoading = false; 16 | bool finalStatus = false; 17 | String _recoveryEmail = ""; 18 | String _otp = ""; 19 | late Object response; 20 | AuthService authService = AuthService(); 21 | late SharedPreferences preferences; 22 | Map resetPasswordInformation = {}; 23 | 24 | bool get isPasswordObscure => _isPasswordObscured; 25 | 26 | bool get isLoading => _isLoading; 27 | 28 | String get recoveryEmail => _recoveryEmail; 29 | 30 | void setLoading(value) { 31 | _isLoading = value; 32 | notifyListeners(); 33 | } 34 | 35 | Future registerUser( 36 | {required String email, 37 | required String firstName, 38 | required String lastName, 39 | required String mobileNumber, 40 | required String password}) async { 41 | finalStatus = false; 42 | setLoading(true); 43 | UserData userData = UserData( 44 | email: email, 45 | firstName: firstName, 46 | lastName: lastName, 47 | mobile: mobileNumber, 48 | password: password, 49 | ); 50 | response = await authService.registration(userData); 51 | (response is Success) ? finalStatus = true : finalStatus = false; 52 | setLoading(false); 53 | return (finalStatus); 54 | } 55 | 56 | Future signInUser( 57 | {required String email, 58 | required String password, 59 | required UserViewModel userViewModel}) async { 60 | finalStatus = false; 61 | setLoading(true); 62 | response = await authService.signIn(email, password); 63 | if (response is Success) { 64 | LoginModel loginModel = LoginModel.fromJson( 65 | (response as Success).response as Map); 66 | finalStatus = true; 67 | preferences = await SharedPreferences.getInstance(); 68 | userViewModel.saveUserData(loginModel, preferences, password); 69 | } else { 70 | finalStatus = false; 71 | } 72 | setLoading(false); 73 | return (finalStatus); 74 | } 75 | 76 | Future sendOTP(String email, {bool isResending = false}) async { 77 | finalStatus = false; 78 | if (!isResending) setLoading(true); 79 | response = await authService.requestOTP(email); 80 | if (response is Success) { 81 | Map status = 82 | (response as Success).response as Map; 83 | if (status['status'] == "success") { 84 | _recoveryEmail = email; 85 | finalStatus = true; 86 | } 87 | } 88 | if (!isResending) setLoading(false); 89 | return finalStatus; 90 | } 91 | 92 | Future verifyOTP(String otp) async { 93 | finalStatus = false; 94 | setLoading(true); 95 | response = await authService.verifyOTP(otp, _recoveryEmail); 96 | if (response is Success) { 97 | Map status = 98 | (response as Success).response as Map; 99 | if (status['status'] == "success") { 100 | _otp = otp; 101 | finalStatus = true; 102 | } 103 | } 104 | setLoading(false); 105 | return finalStatus; 106 | } 107 | 108 | Future resetPassword(String newPassword) async { 109 | finalStatus = false; 110 | setLoading(true); 111 | resetPasswordInformation.putIfAbsent("email", () => _recoveryEmail); 112 | resetPasswordInformation.putIfAbsent("OTP", () => _otp); 113 | resetPasswordInformation.putIfAbsent("password", () => newPassword); 114 | response = await authService.resetPassword(resetPasswordInformation); 115 | if (response is Success) { 116 | Map status = 117 | (response as Success).response as Map; 118 | if (status["status"] == "success") { 119 | resetPasswordInformation = {}; 120 | finalStatus = true; 121 | } 122 | } 123 | setLoading(false); 124 | return finalStatus; 125 | } 126 | 127 | Future authenticateToken(String? token) async { 128 | if (token != null && !JwtDecoder.isExpired(token)) { 129 | return true; 130 | } 131 | return false; 132 | } 133 | 134 | Future signOut() async { 135 | await AppNavigation().signOutUser(); 136 | } 137 | 138 | set setPasswordObscure(bool value) { 139 | _isPasswordObscured = value; 140 | notifyListeners(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/viewModels/countdown_timer_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class CountdownTimerViewModel extends ChangeNotifier { 4 | int _resendTimeLeft = 60; 5 | 6 | int get resendTimeLeft => _resendTimeLeft; 7 | 8 | void updateCountdown() { 9 | _resendTimeLeft--; 10 | notifyListeners(); 11 | } 12 | 13 | void resetCountDown() { 14 | _resendTimeLeft = 60; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/viewModels/dashboard_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class DashboardViewModel extends ChangeNotifier { 4 | int _index = 0; 5 | 6 | int get index => _index; 7 | 8 | set setIndex(int index) { 9 | _index = index; 10 | notifyListeners(); 11 | } 12 | 13 | void refreshViewModel() { 14 | notifyListeners(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/viewModels/task_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:task_manager/models/responseModel/success.dart'; 3 | import 'package:task_manager/models/taskListModel/task_data.dart'; 4 | import 'package:task_manager/models/taskListModel/task_list_model.dart'; 5 | import 'package:task_manager/models/taskStatusCountModels/task_status_count_model.dart'; 6 | import 'package:task_manager/services/task_service.dart'; 7 | import 'package:task_manager/utils/app_strings.dart'; 8 | import 'package:task_manager/viewModels/dashboard_view_model.dart'; 9 | 10 | import '../models/taskStatusCountModels/status_data.dart'; 11 | 12 | class TaskViewModel extends ChangeNotifier { 13 | List _taskStatusData = []; 14 | List taskList = [ 15 | AppStrings.taskStatusNew, 16 | AppStrings.taskStatusCompleted, 17 | AppStrings.taskStatusProgress, 18 | AppStrings.taskStatusCanceled 19 | ]; 20 | Map> _taskDataByStatus = {}; 21 | final Map _badgeCount = { 22 | AppStrings.taskStatusNew: 0, 23 | AppStrings.taskStatusProgress: 0, 24 | AppStrings.taskStatusCompleted: 0, 25 | AppStrings.taskStatusCanceled: 0 26 | }; 27 | Map taskStatusCount = {}; 28 | Map selectedIndex = {}; 29 | bool _isLoading = false; 30 | 31 | late Object response; 32 | TaskService taskService = TaskService(); 33 | 34 | bool get isLoading => _isLoading; 35 | 36 | List get taskStatusData => _taskStatusData; 37 | 38 | Map> get taskDataByStatus => _taskDataByStatus; 39 | 40 | int? getBadgeCount(String taskStatus) => _badgeCount[taskStatus]; 41 | 42 | void setIsLoading(bool value) { 43 | _isLoading = value; 44 | notifyListeners(); 45 | } 46 | 47 | void resetTaskData() { 48 | _taskDataByStatus = {}; 49 | taskStatusCount = {}; 50 | } 51 | 52 | void setIsTileExpanded(String taskStatus, int index, bool value) { 53 | _taskDataByStatus[taskStatus]![index].isTileExpanded = value; 54 | notifyListeners(); 55 | } 56 | 57 | Future fetchTaskStatusData(String token) async { 58 | response = await taskService.fetchTaskStatusCount(token); 59 | if (response is Success) { 60 | TaskStatusCountModel taskStatusCountModel = TaskStatusCountModel.fromJson( 61 | (response as Success).response as Map); 62 | if (taskStatusCountModel.statusData != null && 63 | taskStatusCountModel.statusData!.isNotEmpty) { 64 | _taskStatusData = 65 | List.from(taskStatusCountModel.statusData as Iterable); 66 | taskStatusCount = {}; 67 | for (StatusData data in _taskStatusData) { 68 | if (data.sId != null) { 69 | taskStatusCount[data.sId.toString()] = data.sum.toString(); 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | Future fetchTaskList(String token) async { 77 | for (String taskStatus in taskList) { 78 | response = await taskService.fetchTaskList(taskStatus, token); 79 | if (response is Success) { 80 | TaskListModel taskListModel = TaskListModel.fromJson( 81 | (response as Success).response as Map); 82 | if (taskListModel.taskData != null) { 83 | List taskData = 84 | List.from(taskListModel.taskData as Iterable); 85 | _taskDataByStatus[taskStatus] = taskData.reversed.toList(); 86 | } 87 | } 88 | } 89 | notifyListeners(); 90 | } 91 | 92 | Future createTask( 93 | String token, String taskSubject, String taskDescription) async { 94 | setIsLoading(true); 95 | Map taskData = { 96 | "title": taskSubject, 97 | "description": taskDescription, 98 | "status": AppStrings.taskStatusNew 99 | }; 100 | response = await taskService.createTask(token, taskData); 101 | if (response is Success) { 102 | Map jsonData = 103 | (response as Success).response as Map; 104 | TaskData taskData = TaskData.fromJson(jsonData["data"]); 105 | String? generatedDate = taskData.createdDate?.substring(0, 10); 106 | List date = generatedDate?.split("-") ?? []; 107 | date.insert(0, date.removeAt(2)); 108 | date.insert(1, date.removeAt(2)); 109 | taskData.createdDate = date.join("-"); 110 | _taskDataByStatus[AppStrings.taskStatusNew]?.insert(0, taskData); 111 | taskStatusCount[AppStrings.taskStatusNew] = 112 | ((int.parse(taskStatusCount[AppStrings.taskStatusNew] ?? "0") + 1) 113 | .toString()); 114 | setIsLoading(false); 115 | return true; 116 | } 117 | setIsLoading(false); 118 | return false; 119 | } 120 | 121 | Future updateTask({ 122 | required String token, 123 | required String taskId, 124 | required String taskStatus, 125 | required String currentScreenStatus, 126 | required int index, 127 | required DashboardViewModel dashboardViewModel, 128 | }) async { 129 | setIsLoading(true); 130 | selectedIndex[currentScreenStatus] = index; 131 | response = await taskService.updateTask(token, taskId, taskStatus); 132 | if (response is Success) { 133 | List? tempData = _taskDataByStatus[currentScreenStatus] 134 | ?.where((taskData) => taskData.sId == taskId) 135 | .toList(); 136 | if (tempData != null) { 137 | tempData[0].status = taskStatus; 138 | _taskDataByStatus[currentScreenStatus]! 139 | .removeWhere((taskData) => taskData.sId == taskId); 140 | _taskDataByStatus[taskStatus]?.add(tempData[0]); 141 | _taskDataByStatus[taskStatus]!.reversed.toList(); 142 | selectedIndex[currentScreenStatus] = -1; 143 | int currentStatusCount = 144 | int.tryParse(taskStatusCount[currentScreenStatus]!) ?? 0; 145 | int targetStatusCount = 146 | int.tryParse(taskStatusCount[taskStatus].toString()) ?? 0; 147 | if (currentStatusCount != 0) { 148 | taskStatusCount[currentScreenStatus] = 149 | (currentStatusCount - 1).toString(); 150 | } 151 | taskStatusCount[taskStatus] = (targetStatusCount + 1).toString(); 152 | } 153 | _badgeCount[taskStatus] = (_badgeCount[taskStatus]! + 1); 154 | dashboardViewModel.refreshViewModel(); 155 | setIsLoading(false); 156 | return true; 157 | } 158 | selectedIndex[currentScreenStatus] = -1; 159 | setIsLoading(false); 160 | return false; 161 | } 162 | 163 | void removeBadgeCount(int index, DashboardViewModel dashboardViewModel) { 164 | Map taskIndex = { 165 | 0: AppStrings.taskStatusNew, 166 | 1: AppStrings.taskStatusProgress, 167 | 2: AppStrings.taskStatusCompleted, 168 | 3: AppStrings.taskStatusCanceled 169 | }; //it's placement is sync with index value of bottomNavBar 170 | _badgeCount[taskIndex[index]!] = 0; 171 | dashboardViewModel.refreshViewModel(); 172 | } 173 | 174 | Future deleteTask( 175 | String token, String taskId, String taskStatus, int index) async { 176 | selectedIndex[taskStatus] = index; 177 | notifyListeners(); 178 | response = await taskService.deleteTask(taskId, token); 179 | if (response is Success) { 180 | _taskDataByStatus[taskStatus] 181 | ?.removeWhere((taskData) => taskData.sId == taskId); 182 | selectedIndex[taskStatus] = -1; 183 | taskStatusCount[taskStatus] = 184 | (int.parse(taskStatusCount[taskStatus].toString()) - 1).toString(); 185 | notifyListeners(); 186 | return true; 187 | } else { 188 | selectedIndex[taskStatus] = -1; 189 | notifyListeners(); 190 | return false; 191 | } 192 | } 193 | 194 | void refreshViewModel() { 195 | notifyListeners(); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /lib/viewModels/user_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:image_picker/image_picker.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | import 'package:task_manager/models/loginModels/user_data.dart'; 8 | import 'package:task_manager/models/responseModel/success.dart'; 9 | import 'package:task_manager/services/user_info_service.dart'; 10 | 11 | import '../models/loginModels/login_model.dart'; 12 | 13 | class UserViewModel extends ChangeNotifier { 14 | String _token = ""; 15 | bool _isLoading = false; 16 | bool _isPasswordObscured = true; 17 | final ImagePicker _pickedImage = ImagePicker(); 18 | String imageName = ""; 19 | String base64Image = ""; 20 | late Object response; 21 | UserData _userData = UserData( 22 | email: "", 23 | firstName: "", 24 | lastName: "", 25 | mobile: "", 26 | password: "", 27 | ); 28 | 29 | bool get isPasswordObscured => _isPasswordObscured; 30 | 31 | bool get isLoading => _isLoading; 32 | 33 | UserData get userData => _userData; 34 | 35 | String get token => _token; 36 | 37 | set setIsPasswordObscured(bool value) { 38 | _isPasswordObscured = value; 39 | notifyListeners(); 40 | } 41 | 42 | set setToken(String token) => _token = token; 43 | 44 | set setIsLoading(bool isLoading) { 45 | _isLoading = isLoading; 46 | notifyListeners(); 47 | } 48 | 49 | set setUserData(UserData userData) { 50 | _userData = userData; 51 | notifyListeners(); 52 | } 53 | 54 | Future loadUserData(SharedPreferences preferences) async { 55 | setToken = preferences.getString("token")!; 56 | setUserData = 57 | UserData.fromJson(jsonDecode(preferences.getString("userData")!)); 58 | } 59 | 60 | void saveUserData( 61 | LoginModel loginModel, SharedPreferences preferences, String password) { 62 | loginModel.data!.password = password; 63 | preferences.setString("token", loginModel.token.toString()); 64 | preferences.setString("userData", jsonEncode(loginModel.data!.toJson())); 65 | setToken = loginModel.token.toString(); 66 | setUserData = loginModel.data!; 67 | } 68 | 69 | Future getImageFromGallery() async { 70 | XFile? pickedFile = await _pickedImage.pickImage( 71 | source: ImageSource.gallery, 72 | imageQuality: 80, 73 | ); 74 | if (pickedFile != null) { 75 | imageName = pickedFile.name; 76 | convertImage(pickedFile); 77 | notifyListeners(); 78 | } 79 | } 80 | 81 | Future updateUserData({ 82 | required String email, 83 | required String firstName, 84 | required String lastName, 85 | required String mobile, 86 | required String password, 87 | }) async { 88 | setIsLoading = true; 89 | SharedPreferences preferences = await SharedPreferences.getInstance(); 90 | if (base64Image.isEmpty) { 91 | base64Image = _userData.photo!; 92 | } 93 | UserData userData = UserData( 94 | email: email, 95 | firstName: firstName, 96 | lastName: lastName, 97 | mobile: mobile, 98 | password: password, 99 | photo: base64Image, 100 | ); 101 | response = await UserInfoService.updateUserProfile(token, userData); 102 | if (response is Success) { 103 | _userData = userData; 104 | preferences.setString("userData", jsonEncode(userData.toJson())); 105 | base64Image = ""; 106 | imageName = ""; 107 | setIsLoading = false; 108 | return true; 109 | } 110 | base64Image = ""; 111 | imageName = ""; 112 | setIsLoading = false; 113 | return false; 114 | } 115 | 116 | void convertImage(XFile pickedFile) { 117 | List imageBytes = File(pickedFile.path).readAsBytesSync(); 118 | base64Image = base64Encode(imageBytes); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/views/authScreens/forgetPasswordScreen/emailVerificationScreen/email_verification_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:gap/gap.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:task_manager/models/responseModel/failure.dart'; 6 | import 'package:task_manager/utils/app_color.dart'; 7 | import 'package:task_manager/utils/app_routes.dart'; 8 | import 'package:task_manager/utils/app_strings.dart'; 9 | import 'package:task_manager/viewModels/auth_view_model.dart'; 10 | import 'package:task_manager/views/widgets/app_snackbar.dart'; 11 | import 'package:task_manager/views/widgets/app_textfield.dart'; 12 | import 'package:task_manager/views/widgets/forget_password_layout.dart'; 13 | 14 | class EmailVerificationScreen extends StatefulWidget { 15 | const EmailVerificationScreen({super.key}); 16 | 17 | @override 18 | State createState() => 19 | _EmailVerificationScreenState(); 20 | } 21 | 22 | class _EmailVerificationScreenState extends State { 23 | late final GlobalKey _formKey; 24 | late final TextEditingController _emailTEController; 25 | late final FocusNode _emailFocusNode; 26 | 27 | @override 28 | void initState() { 29 | _emailTEController = TextEditingController(); 30 | _formKey = GlobalKey(); 31 | _emailFocusNode = FocusNode(); 32 | super.initState(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | double screenHeight = MediaQuery.of(context).size.height; 38 | double screenWidth = MediaQuery.of(context).size.width; 39 | return Scaffold( 40 | body: OrientationBuilder( 41 | builder: (BuildContext context, Orientation orientation) { 42 | return ForgetPasswordLayout( 43 | orientation: orientation, 44 | horizontalMargin: screenWidth * 0.1, 45 | verticalMargin: (orientation == Orientation.portrait) 46 | ? screenHeight * 0.25 47 | : screenHeight * 0.15, 48 | headerText: AppStrings.emailVerificationHeaderText, 49 | bodyText: AppStrings.emailVerificationBodyText, 50 | screenWidth: screenWidth, 51 | buttonWidget: const Icon( 52 | Icons.arrow_circle_right_outlined, 53 | size: 30, 54 | ), 55 | onButtonPressed: (value) { 56 | if (_formKey.currentState!.validate()) { 57 | initiateOTPSending(); 58 | } 59 | }, 60 | child: Form( 61 | key: _formKey, 62 | child: Column( 63 | children: [ 64 | AppTextField( 65 | focusNode: _emailFocusNode, 66 | controller: _emailTEController, 67 | hintText: AppStrings.emailTextFieldHint, 68 | inputType: TextInputType.emailAddress, 69 | errorText: AppStrings.emailErrorText, 70 | regEx: AppStrings.emailRegEx, 71 | ), 72 | const Gap(20), 73 | ], 74 | ), 75 | ), 76 | ); 77 | }, 78 | ), 79 | ); 80 | } 81 | 82 | Future initiateOTPSending() async { 83 | bool status = await context 84 | .read() 85 | .sendOTP(_emailTEController.text.trim()); 86 | if (status && mounted) { 87 | Navigator.pushReplacementNamed(context, AppRoutes.pinVerificationScreen); 88 | return; 89 | } 90 | if (mounted) { 91 | int statusCode = 92 | (context.read().response as Failure).statusCode; 93 | AppSnackBar().showSnackBar( 94 | title: AppStrings.sendOTPFailureTitle, 95 | content: (statusCode == 600) 96 | ? AppStrings.serverConnectionErrorText 97 | : AppStrings.sendOTPFailureMessage, 98 | contentType: ContentType.failure, 99 | color: AppColor.snackBarFailureColor, 100 | context: context); 101 | } 102 | } 103 | 104 | @override 105 | void dispose() { 106 | _emailTEController.dispose(); 107 | _emailFocusNode.dispose(); 108 | super.dispose(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/views/authScreens/forgetPasswordScreen/pinVerificationScreen/pin_verification_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../widgets/app_textfield.dart'; 5 | 6 | class PinVerificationForm extends StatelessWidget { 7 | final List pinTEControllers; 8 | final List focusNodes; 9 | final GlobalKey formKey; 10 | final double textFieldHeight = 50; 11 | final double textFieldWidth; 12 | 13 | const PinVerificationForm( 14 | {super.key, 15 | required this.textFieldWidth, 16 | required this.pinTEControllers, 17 | required this.focusNodes, 18 | required this.formKey}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | Orientation orientation = MediaQuery.orientationOf(context); 23 | return Form( 24 | key: formKey, 25 | child: Row( 26 | mainAxisAlignment: MainAxisAlignment.start, 27 | children: List.generate(focusNodes.length, (index) { 28 | return Padding( 29 | padding: EdgeInsets.only( 30 | left: (orientation == Orientation.portrait) ? 8 : 18), 31 | child: SizedBox( 32 | width: textFieldWidth, 33 | height: textFieldHeight, 34 | child: AppTextField( 35 | focusNode: focusNodes[index], 36 | controller: pinTEControllers[index], 37 | inputType: TextInputType.number, 38 | maxLength: 1, 39 | textAlign: TextAlign.center, 40 | textStyle: const TextStyle( 41 | fontSize: 22, 42 | fontWeight: FontWeight.bold, 43 | ), 44 | disableValidation: true, 45 | onChanged: (value) { 46 | if (value.isNotEmpty) { 47 | if (index == focusNodes.length - 1) { 48 | FocusScope.of(context).unfocus(); 49 | } else { 50 | FocusScope.of(context) 51 | .requestFocus(focusNodes[index + 1]); 52 | } 53 | } 54 | }, 55 | ), 56 | ), 57 | ); 58 | }), 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/views/authScreens/forgetPasswordScreen/pinVerificationScreen/pin_verification_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:gap/gap.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:task_manager/utils/app_color.dart'; 8 | import 'package:task_manager/utils/app_strings.dart'; 9 | import 'package:task_manager/viewModels/auth_view_model.dart'; 10 | import 'package:task_manager/viewModels/countdown_timer_view_model.dart'; 11 | import 'package:task_manager/views/authScreens/forgetPasswordScreen/pinVerificationScreen/pin_verification_form.dart'; 12 | import 'package:task_manager/views/authScreens/forgetPasswordScreen/pinVerificationScreen/resend_pin_layout.dart'; 13 | import 'package:task_manager/views/widgets/app_snackbar.dart'; 14 | import 'package:task_manager/views/widgets/forget_password_layout.dart'; 15 | 16 | import '../../../../utils/app_routes.dart'; 17 | 18 | class PinVerificationScreen extends StatefulWidget { 19 | const PinVerificationScreen({super.key}); 20 | 21 | @override 22 | State createState() => _PinVerificationScreenState(); 23 | } 24 | 25 | class _PinVerificationScreenState extends State { 26 | late final List pinTEControllers; 27 | 28 | late final List focusNodes; 29 | late GlobalKey _formKey; 30 | final double textFieldHeight = 50; 31 | late Timer timer; 32 | 33 | @override 34 | void initState() { 35 | pinTEControllers = List.generate(6, (index) => TextEditingController()); 36 | focusNodes = List.generate(6, (index) => FocusNode()); 37 | _formKey = GlobalKey(); 38 | super.initState(); 39 | startCountDownTimer(); 40 | } 41 | 42 | void startCountDownTimer() async { 43 | context.read().resetCountDown(); 44 | timer = Timer.periodic(const Duration(seconds: 1), (countdown) { 45 | if (countdown.tick > 60) { 46 | countdown.cancel(); 47 | return; 48 | } 49 | context.read().updateCountdown(); 50 | }); 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | double screenHeight = MediaQuery.of(context).size.height; 56 | double screenWidth = MediaQuery.of(context).size.width; 57 | return Scaffold( 58 | body: OrientationBuilder( 59 | builder: (BuildContext context, Orientation orientation) { 60 | return ForgetPasswordLayout( 61 | orientation: orientation, 62 | horizontalMargin: screenWidth * 0.1, 63 | verticalMargin: (orientation == Orientation.portrait) 64 | ? screenHeight * 0.25 65 | : screenHeight * 0.15, 66 | headerText: AppStrings.pinVerificationHeaderText, 67 | bodyText: AppStrings.pinVerificationBodyText, 68 | screenWidth: screenWidth, 69 | buttonWidget: const Text( 70 | AppStrings.pinVerificationButtonText, 71 | ), 72 | onButtonPressed: (value) { 73 | if (_formKey.currentState!.validate()) { 74 | initiatePinVerification(); 75 | return; 76 | } 77 | AppSnackBar().showSnackBar( 78 | title: AppStrings.emptyPinVerificationFieldTitle, 79 | content: AppStrings.emptyPinVerificationFieldMessage, 80 | contentType: ContentType.warning, 81 | color: AppColor.snackBarWarningColor, 82 | context: context); 83 | }, 84 | child: Column( 85 | children: [ 86 | PinVerificationForm( 87 | formKey: _formKey, 88 | textFieldWidth: (orientation == Orientation.portrait) 89 | ? screenWidth * 0.11 90 | : screenWidth * 0.09, 91 | pinTEControllers: pinTEControllers, 92 | focusNodes: focusNodes, 93 | ), 94 | const Gap(5), 95 | Consumer(builder: (_, viewModel, __) { 96 | return Padding( 97 | padding: const EdgeInsets.all(8.0), 98 | child: ResendPinLayout( 99 | resendTimeLeft: viewModel.resendTimeLeft, 100 | email: context.read().recoveryEmail, 101 | restartTimer: startCountDownTimer, 102 | ), 103 | ); 104 | }) 105 | ], 106 | ), 107 | ); 108 | }, 109 | ), 110 | ); 111 | } 112 | 113 | Future initiatePinVerification() async { 114 | String otp = ""; 115 | for (TextEditingController controller in pinTEControllers) { 116 | otp = otp + controller.text.trim(); 117 | } 118 | bool status = await context.read().verifyOTP(otp); 119 | if (status && mounted) { 120 | timer.cancel(); 121 | Navigator.pushReplacementNamed(context, AppRoutes.setPasswordScreen); 122 | return; 123 | } 124 | if (mounted) { 125 | AppSnackBar().showSnackBar( 126 | title: AppStrings.wrongPinVerificationFieldTitle, 127 | content: AppStrings.wrongPinVerificationFieldMessage, 128 | contentType: ContentType.failure, 129 | color: AppColor.snackBarFailureColor, 130 | context: context); 131 | } 132 | } 133 | 134 | @override 135 | void dispose() { 136 | for (TextEditingController controller in pinTEControllers) { 137 | controller.dispose(); 138 | } 139 | for (FocusNode focusNode in focusNodes) { 140 | focusNode.dispose(); 141 | } 142 | super.dispose(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/views/authScreens/forgetPasswordScreen/pinVerificationScreen/resend_pin_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:task_manager/utils/app_color.dart'; 4 | import 'package:task_manager/viewModels/auth_view_model.dart'; 5 | 6 | class ResendPinLayout extends StatelessWidget { 7 | final int resendTimeLeft; 8 | final String email; 9 | final Function restartTimer; 10 | 11 | const ResendPinLayout( 12 | {super.key, 13 | required this.resendTimeLeft, 14 | required this.email, 15 | required this.restartTimer}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Row( 20 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 21 | children: [ 22 | Text((resendTimeLeft == 60) 23 | ? "1:00" 24 | : "0:${resendTimeLeft.toString().padLeft(2, "0")}"), 25 | InkWell( 26 | splashColor: Colors.transparent, 27 | onTap: () async { 28 | if (resendTimeLeft != 0) { 29 | return; 30 | } 31 | await context 32 | .read() 33 | .sendOTP(email, isResending: true); 34 | restartTimer(); 35 | }, 36 | child: Text( 37 | "Resend", 38 | style: TextStyle( 39 | color: (resendTimeLeft == 0) 40 | ? AppColor.appPrimaryColor 41 | : Colors.grey, 42 | ), 43 | ), 44 | ), 45 | ], 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/views/authScreens/forgetPasswordScreen/setPasswordScreen/set_password_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:gap/gap.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:task_manager/models/responseModel/failure.dart'; 6 | import 'package:task_manager/utils/app_color.dart'; 7 | import 'package:task_manager/utils/app_strings.dart'; 8 | import 'package:task_manager/viewModels/auth_view_model.dart'; 9 | import 'package:task_manager/views/widgets/app_snackbar.dart'; 10 | import 'package:task_manager/views/widgets/app_textfield.dart'; 11 | import 'package:task_manager/views/widgets/forget_password_layout.dart'; 12 | 13 | class SetPasswordScreen extends StatefulWidget { 14 | const SetPasswordScreen({super.key}); 15 | 16 | @override 17 | State createState() => _SetPasswordScreenState(); 18 | } 19 | 20 | class _SetPasswordScreenState extends State { 21 | late final TextEditingController _passwordTEController; 22 | late final TextEditingController _confirmPasswordTEController; 23 | late final FocusNode _passwordFocusNode; 24 | late final FocusNode _confirmPasswordFocusNode; 25 | late final GlobalKey _formKey; 26 | 27 | @override 28 | void initState() { 29 | _passwordFocusNode = FocusNode(); 30 | _confirmPasswordFocusNode = FocusNode(); 31 | _passwordTEController = TextEditingController(); 32 | _confirmPasswordTEController = TextEditingController(); 33 | _formKey = GlobalKey(); 34 | super.initState(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | double screenHeight = MediaQuery.of(context).size.height; 40 | double screenWidth = MediaQuery.of(context).size.width; 41 | return Scaffold( 42 | body: OrientationBuilder( 43 | builder: (BuildContext context, Orientation orientation) { 44 | return ForgetPasswordLayout( 45 | orientation: orientation, 46 | horizontalMargin: screenWidth * 0.1, 47 | verticalMargin: (orientation == Orientation.portrait) 48 | ? screenHeight * 0.25 49 | : screenHeight * 0.15, 50 | headerText: AppStrings.setPasswordHeaderText, 51 | bodyText: AppStrings.setPasswordBodyText, 52 | screenWidth: screenWidth, 53 | buttonWidget: const Text(AppStrings.setPasswordButtonText), 54 | onButtonPressed: (value) { 55 | if (_formKey.currentState!.validate()) { 56 | initiatePasswordReset(); 57 | } 58 | }, 59 | child: Form( 60 | key: _formKey, 61 | child: Column( 62 | children: [ 63 | AppTextField( 64 | focusNode: _passwordFocusNode, 65 | controller: _passwordTEController, 66 | inputType: TextInputType.text, 67 | hintText: AppStrings.passwordTextFieldHint, 68 | errorText: AppStrings.passwordErrorText, 69 | setCustomValidation: true, 70 | customValidation: (String value) { 71 | if (_passwordTEController.text.length < 8 || 72 | value.isEmpty) { 73 | return AppStrings.passwordLengthErrorText; 74 | } 75 | return null; 76 | }, 77 | onFieldSubmitted: (value) { 78 | if (value.isNotEmpty) { 79 | FocusScope.of(context) 80 | .requestFocus(_confirmPasswordFocusNode); 81 | } 82 | }, 83 | ), 84 | const Gap(10), 85 | AppTextField( 86 | focusNode: _confirmPasswordFocusNode, 87 | controller: _confirmPasswordTEController, 88 | inputType: TextInputType.text, 89 | hintText: AppStrings.confirmPassTextFieldHint, 90 | onFieldSubmitted: (value) { 91 | if (value.isNotEmpty) { 92 | FocusScope.of(context).unfocus(); 93 | } 94 | }, 95 | setCustomValidation: true, 96 | customValidation: (String value) { 97 | if (value.isEmpty) { 98 | return AppStrings.confirmPasswordErrorText; 99 | } 100 | if (_passwordTEController.text != 101 | _confirmPasswordTEController.text) { 102 | return AppStrings.confirmPasswordErrorText; 103 | } 104 | return null; 105 | }, 106 | ), 107 | ], 108 | ), 109 | ), 110 | ); 111 | }, 112 | ), 113 | ); 114 | } 115 | 116 | Future initiatePasswordReset() async { 117 | bool status = await context 118 | .read() 119 | .resetPassword(_passwordTEController.text.trim()); 120 | if (status && mounted) { 121 | AppSnackBar().showSnackBar( 122 | title: AppStrings.resetPasswordSuccessTitle, 123 | content: AppStrings.resetPasswordSuccessMessage, 124 | contentType: ContentType.success, 125 | color: AppColor.snackBarSuccessColor, 126 | context: context); 127 | Navigator.pop(context); 128 | return; 129 | } 130 | if (mounted) { 131 | Failure failure = context.read().response as Failure; 132 | AppSnackBar().showSnackBar( 133 | title: AppStrings.resetPasswordFailureTitle, 134 | content: failure.message, 135 | contentType: ContentType.failure, 136 | color: AppColor.snackBarFailureColor, 137 | context: context); 138 | } 139 | } 140 | 141 | @override 142 | void dispose() { 143 | _passwordTEController.dispose(); 144 | _confirmPasswordTEController.dispose(); 145 | _confirmPasswordFocusNode.dispose(); 146 | _passwordFocusNode.dispose(); 147 | super.dispose(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/views/authScreens/signInScreen/sign_in_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 2 | import 'package:flutter/gestures.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:gap/gap.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:task_manager/models/responseModel/failure.dart'; 7 | import 'package:task_manager/utils/app_color.dart'; 8 | import 'package:task_manager/utils/app_navigation.dart'; 9 | import 'package:task_manager/utils/app_routes.dart'; 10 | import 'package:task_manager/utils/app_strings.dart'; 11 | import 'package:task_manager/viewModels/auth_view_model.dart'; 12 | import 'package:task_manager/viewModels/task_view_model.dart'; 13 | import 'package:task_manager/viewModels/user_view_model.dart'; 14 | import 'package:task_manager/views/authScreens/signInScreen/sign_in_screen_form.dart'; 15 | import 'package:task_manager/views/widgets/app_snackbar.dart'; 16 | 17 | import '../../widgets/background_widget.dart'; 18 | 19 | class SignInScreen extends StatefulWidget { 20 | const SignInScreen({super.key}); 21 | 22 | @override 23 | State createState() => _SignInScreenState(); 24 | } 25 | 26 | class _SignInScreenState extends State { 27 | late final TextEditingController _emailTEController; 28 | late final TextEditingController _passwordTEController; 29 | late final GlobalKey _formKey; 30 | late final FocusNode _emailFocusNode = FocusNode(); 31 | late final FocusNode _passwordFocusNode = FocusNode(); 32 | 33 | @override 34 | void initState() { 35 | _emailTEController = TextEditingController(); 36 | _passwordTEController = TextEditingController(); 37 | _formKey = GlobalKey(); 38 | super.initState(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | double screenHeight = MediaQuery.of(context).size.height; 44 | double screenWidth = MediaQuery.of(context).size.width; 45 | return Scaffold( 46 | body: OrientationBuilder( 47 | builder: (BuildContext context, Orientation orientation) { 48 | return BackgroundWidget( 49 | childWidget: SingleChildScrollView( 50 | child: Container( 51 | margin: EdgeInsets.symmetric( 52 | horizontal: screenWidth * 0.1, 53 | vertical: (orientation == Orientation.portrait) 54 | ? screenHeight * 0.25 55 | : screenHeight * 0.05), 56 | child: Column( 57 | mainAxisAlignment: MainAxisAlignment.start, 58 | crossAxisAlignment: CrossAxisAlignment.start, 59 | children: [ 60 | Text( 61 | AppStrings.signInHeaderText, 62 | style: Theme.of(context).textTheme.headlineLarge, 63 | ), 64 | const Gap(20), 65 | SizedBox( 66 | width: screenWidth * 0.8, 67 | child: SignInScreenForm( 68 | emailTEController: _emailTEController, 69 | passwordTEController: _passwordTEController, 70 | formKey: _formKey, 71 | emailFocusNode: _emailFocusNode, 72 | passwordFocusNode: _passwordFocusNode, 73 | screenWidth: screenWidth, 74 | initiateSignIn: initiateSignIn, 75 | ), 76 | ), 77 | const Gap(50), 78 | Center( 79 | child: InkWell( 80 | splashColor: Colors.transparent, 81 | onTap: () => Navigator.pushNamed( 82 | context, AppRoutes.emailVerificationScreen), 83 | child: Text( 84 | AppStrings.forgetPasswordText, 85 | style: Theme.of(context).textTheme.bodySmall, 86 | ), 87 | ), 88 | ), 89 | const Gap(10), 90 | Center( 91 | child: RichText( 92 | text: TextSpan( 93 | text: AppStrings.signInBottomTextOne, 94 | style: Theme.of(context).textTheme.bodyMedium, 95 | children: [ 96 | TextSpan( 97 | text: AppStrings.signInBottomTextTwo, 98 | recognizer: TapGestureRecognizer() 99 | ..onTap = () => AppNavigation().gotoSignUp( 100 | _emailFocusNode, _passwordFocusNode), 101 | style: const TextStyle( 102 | color: AppColor.appPrimaryColor, 103 | ), 104 | ), 105 | ], 106 | ), 107 | ), 108 | ), 109 | ], 110 | ), 111 | ), 112 | ), 113 | ); 114 | }, 115 | ), 116 | ); 117 | } 118 | 119 | Future initiateSignIn() async { 120 | bool status = await context.read().signInUser( 121 | email: _emailTEController.text.trim(), 122 | password: _passwordTEController.text, 123 | userViewModel: context.read()); 124 | if (mounted && status) { 125 | context.read().resetTaskData(); 126 | Navigator.pushReplacementNamed(context, AppRoutes.dashboardScreen); 127 | context.read().setPasswordObscure = true; 128 | return; 129 | } 130 | if (mounted) { 131 | Failure failure = context.read().response as Failure; 132 | AppSnackBar().showSnackBar( 133 | title: AppStrings.signInFailureTitle, 134 | content: failure.message, 135 | contentType: ContentType.failure, 136 | color: AppColor.snackBarFailureColor, 137 | context: context); 138 | } 139 | } 140 | 141 | @override 142 | void dispose() { 143 | _emailTEController.dispose(); 144 | _passwordTEController.dispose(); 145 | _emailFocusNode.dispose(); 146 | _passwordFocusNode.dispose(); 147 | super.dispose(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/views/authScreens/signInScreen/sign_in_screen_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../../../utils/app_color.dart'; 6 | import '../../../utils/app_strings.dart'; 7 | import '../../../viewModels/auth_view_model.dart'; 8 | import '../../widgets/app_textfield.dart'; 9 | import '../../widgets/circular_progressbar.dart'; 10 | 11 | class SignInScreenForm extends StatelessWidget { 12 | final TextEditingController emailTEController; 13 | final TextEditingController passwordTEController; 14 | final GlobalKey formKey; 15 | final FocusNode emailFocusNode, passwordFocusNode; 16 | final double screenWidth; 17 | final Function initiateSignIn; 18 | 19 | const SignInScreenForm( 20 | {super.key, 21 | required this.emailTEController, 22 | required this.passwordTEController, 23 | required this.formKey, 24 | required this.emailFocusNode, 25 | required this.passwordFocusNode, 26 | required this.screenWidth, 27 | required this.initiateSignIn}); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Form( 32 | key: formKey, 33 | child: Column( 34 | children: [ 35 | AppTextField( 36 | inputType: TextInputType.emailAddress, 37 | focusNode: emailFocusNode, 38 | controller: emailTEController, 39 | hintText: AppStrings.emailTextFieldHint, 40 | regEx: AppStrings.emailRegEx, 41 | onFieldSubmitted: (value) { 42 | FocusScope.of(context).requestFocus(passwordFocusNode); 43 | }, 44 | errorText: AppStrings.emailErrorText, 45 | ), 46 | const Gap(15), 47 | Consumer( 48 | builder: (_, viewModel, __) => AppTextField( 49 | inputType: TextInputType.visiblePassword, 50 | focusNode: passwordFocusNode, 51 | controller: passwordTEController, 52 | hintText: AppStrings.passwordTextFieldHint, 53 | suffixIcon: InkWell( 54 | splashColor: Colors.transparent, 55 | onTap: () { 56 | viewModel.setPasswordObscure = !viewModel.isPasswordObscure; 57 | }, 58 | child: (viewModel.isPasswordObscure) 59 | ? const Icon(Icons.visibility, 60 | color: AppColor.appPrimaryColor) 61 | : const Icon(Icons.visibility_off, 62 | color: AppColor.appPrimaryColor), 63 | ), 64 | isObscureText: viewModel.isPasswordObscure, 65 | errorText: AppStrings.passwordErrorText, 66 | ), 67 | ), 68 | const Gap(20), 69 | SizedBox( 70 | width: screenWidth * 0.9, 71 | child: Consumer( 72 | builder: (_, viewModel, __) { 73 | return ElevatedButton( 74 | onPressed: () { 75 | if (formKey.currentState!.validate() && 76 | !viewModel.isLoading) { 77 | initiateSignIn(); 78 | } 79 | FocusScope.of(context).unfocus(); 80 | }, 81 | child: viewModel.isLoading 82 | ? const CircularProgressbar( 83 | color: AppColor.circularProgressbarColor) 84 | : const Icon( 85 | Icons.arrow_circle_right_outlined, 86 | size: 30, 87 | ), 88 | ); 89 | }, 90 | ), 91 | ), 92 | ], 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/views/authScreens/signUpScreen/sign_up_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:task_manager/views/widgets/circular_progressbar.dart'; 5 | 6 | import '../../../utils/app_color.dart'; 7 | import '../../../utils/app_strings.dart'; 8 | import '../../../viewModels/auth_view_model.dart'; 9 | import '../../widgets/app_textfield.dart'; 10 | 11 | class SignUpForm extends StatelessWidget { 12 | final TextEditingController emailTEController; 13 | final TextEditingController passwordTEController; 14 | final TextEditingController firstNameTEController; 15 | final TextEditingController lastNameTEController; 16 | final TextEditingController mobileNumberTEController; 17 | final GlobalKey formKey; 18 | final FocusNode emailFocusNode, 19 | passwordFocusNode, 20 | firstNameFocusNode, 21 | lastNameFocusNode, 22 | mobileNumberFocusNode; 23 | final Function registerUser; 24 | final double screenWidth; 25 | 26 | const SignUpForm( 27 | {super.key, 28 | required this.emailTEController, 29 | required this.passwordTEController, 30 | required this.formKey, 31 | required this.emailFocusNode, 32 | required this.passwordFocusNode, 33 | required this.screenWidth, 34 | required this.firstNameTEController, 35 | required this.lastNameTEController, 36 | required this.mobileNumberTEController, 37 | required this.firstNameFocusNode, 38 | required this.lastNameFocusNode, 39 | required this.mobileNumberFocusNode, 40 | required this.registerUser}); 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Form( 45 | key: formKey, 46 | child: Column( 47 | children: [ 48 | AppTextField( 49 | inputType: TextInputType.emailAddress, 50 | focusNode: emailFocusNode, 51 | controller: emailTEController, 52 | hintText: AppStrings.emailTextFieldHint, 53 | regEx: AppStrings.emailRegEx, 54 | onFieldSubmitted: (value) { 55 | FocusScope.of(context).requestFocus(firstNameFocusNode); 56 | }, 57 | errorText: AppStrings.emailErrorText, 58 | ), 59 | const Gap(15), 60 | AppTextField( 61 | inputType: TextInputType.name, 62 | focusNode: firstNameFocusNode, 63 | controller: firstNameTEController, 64 | hintText: AppStrings.firstNameTextFieldHint, 65 | regEx: AppStrings.nameRegEX, 66 | onFieldSubmitted: (value) { 67 | FocusScope.of(context).requestFocus(lastNameFocusNode); 68 | }, 69 | errorText: AppStrings.firstNameErrorText, 70 | ), 71 | const Gap(15), 72 | AppTextField( 73 | inputType: TextInputType.name, 74 | focusNode: lastNameFocusNode, 75 | controller: lastNameTEController, 76 | hintText: AppStrings.lastNameTextFieldHint, 77 | regEx: AppStrings.nameRegEX, 78 | onFieldSubmitted: (value) { 79 | FocusScope.of(context).requestFocus(mobileNumberFocusNode); 80 | }, 81 | errorText: AppStrings.lastNameErrorText, 82 | ), 83 | const Gap(15), 84 | AppTextField( 85 | inputType: TextInputType.number, 86 | focusNode: mobileNumberFocusNode, 87 | controller: mobileNumberTEController, 88 | hintText: AppStrings.mobileNumberTextFieldHint, 89 | regEx: AppStrings.phoneNumberRegEx, 90 | maxLength: 14, 91 | onFieldSubmitted: (value) { 92 | FocusScope.of(context).requestFocus(passwordFocusNode); 93 | }, 94 | errorText: AppStrings.mobileNumberErrorText, 95 | ), 96 | const Gap(15), 97 | Consumer( 98 | builder: (_, viewModel, __) => AppTextField( 99 | inputType: TextInputType.visiblePassword, 100 | focusNode: passwordFocusNode, 101 | controller: passwordTEController, 102 | hintText: AppStrings.passwordTextFieldHint, 103 | suffixIcon: getPasswordFieldIcon(viewModel), 104 | isObscureText: viewModel.isPasswordObscure, 105 | errorText: AppStrings.passwordErrorText, 106 | ), 107 | ), 108 | const Gap(20), 109 | SizedBox( 110 | width: screenWidth * 0.9, 111 | child: Consumer( 112 | builder: (_, viewModel, __) { 113 | return ElevatedButton( 114 | onPressed: () { 115 | if (formKey.currentState!.validate() && 116 | !viewModel.isLoading) { 117 | registerUser(); 118 | } 119 | FocusScope.of(context).unfocus(); 120 | }, 121 | child: viewModel.isLoading 122 | ? const CircularProgressbar( 123 | color: AppColor.circularProgressbarColor) 124 | : const Icon( 125 | Icons.arrow_circle_right_outlined, 126 | size: 30, 127 | ), 128 | ); 129 | }, 130 | ), 131 | ), 132 | ], 133 | ), 134 | ); 135 | } 136 | 137 | IconButton getPasswordFieldIcon(AuthViewModel viewModel) { 138 | IconData icon; 139 | (viewModel.isPasswordObscure) 140 | ? icon = Icons.visibility 141 | : icon = Icons.visibility_off; 142 | return IconButton( 143 | splashColor: Colors.transparent, 144 | onPressed: () { 145 | (viewModel.isPasswordObscure) 146 | ? viewModel.setPasswordObscure = false 147 | : viewModel.setPasswordObscure = true; 148 | }, 149 | icon: Icon(icon, color: AppColor.appPrimaryColor), 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /lib/views/authScreens/signUpScreen/sign_up_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:gap/gap.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:task_manager/utils/app_strings.dart'; 6 | import 'package:task_manager/views/authScreens/signUpScreen/sign_up_form.dart'; 7 | import 'package:task_manager/views/widgets/app_snackbar.dart'; 8 | import 'package:task_manager/views/widgets/background_widget.dart'; 9 | import 'package:task_manager/views/widgets/sign_in_bottom_text.dart'; 10 | 11 | import '../../../utils/app_color.dart'; 12 | import '../../../viewModels/auth_view_model.dart'; 13 | 14 | class SignUpScreen extends StatefulWidget { 15 | const SignUpScreen({super.key}); 16 | 17 | @override 18 | State createState() => _SignUpScreenState(); 19 | } 20 | 21 | class _SignUpScreenState extends State { 22 | late final TextEditingController _emailTEController, 23 | _firstNameTEController, 24 | _lastNameTEController, 25 | _mobileNumberTEController, 26 | _passwordTEController; 27 | late final GlobalKey _formKey; 28 | late final FocusNode _emailFocusNode, 29 | _passwordFocusNode, 30 | _firstNameFocusNode, 31 | _lastNameFocusNode, 32 | _mobileNumberFocusNode; 33 | 34 | @override 35 | void initState() { 36 | _emailTEController = TextEditingController(); 37 | _firstNameTEController = TextEditingController(); 38 | _lastNameTEController = TextEditingController(); 39 | _mobileNumberTEController = TextEditingController(); 40 | _passwordTEController = TextEditingController(); 41 | _formKey = GlobalKey(); 42 | _emailFocusNode = FocusNode(); 43 | _passwordFocusNode = FocusNode(); 44 | _firstNameFocusNode = FocusNode(); 45 | _lastNameFocusNode = FocusNode(); 46 | _mobileNumberFocusNode = FocusNode(); 47 | super.initState(); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | double screenWidth = MediaQuery.of(context).size.width; 53 | double screenHeight = MediaQuery.of(context).size.height; 54 | return Scaffold(body: OrientationBuilder( 55 | builder: (BuildContext context, Orientation orientation) { 56 | return BackgroundWidget( 57 | childWidget: SingleChildScrollView( 58 | child: Container( 59 | margin: EdgeInsets.symmetric( 60 | horizontal: screenWidth * 0.1, 61 | vertical: (orientation == Orientation.portrait) 62 | ? screenHeight * 0.2 63 | : screenHeight * 0.05, 64 | ), 65 | child: Column( 66 | crossAxisAlignment: CrossAxisAlignment.start, 67 | children: [ 68 | Text( 69 | AppStrings.signUpHeaderText, 70 | style: Theme.of(context).textTheme.headlineLarge, 71 | ), 72 | const Gap(20), 73 | SignUpForm( 74 | emailTEController: _emailTEController, 75 | passwordTEController: _passwordTEController, 76 | formKey: _formKey, 77 | emailFocusNode: _emailFocusNode, 78 | passwordFocusNode: _passwordFocusNode, 79 | screenWidth: screenWidth, 80 | firstNameTEController: _firstNameTEController, 81 | lastNameTEController: _lastNameTEController, 82 | mobileNumberTEController: _mobileNumberTEController, 83 | firstNameFocusNode: _firstNameFocusNode, 84 | lastNameFocusNode: _lastNameFocusNode, 85 | mobileNumberFocusNode: _mobileNumberFocusNode, 86 | registerUser: registerUser, 87 | ), 88 | const Gap(30), 89 | SignInBottomText(route: gotoSignIn) 90 | ], 91 | ), 92 | ), 93 | ), 94 | ); 95 | }, 96 | )); 97 | } 98 | 99 | Future registerUser() async { 100 | final AuthViewModel authViewModel = 101 | Provider.of(context, listen: false); 102 | bool status = await authViewModel.registerUser( 103 | email: _emailTEController.text.trim(), 104 | firstName: _firstNameTEController.text.trim(), 105 | lastName: _lastNameTEController.text.trim(), 106 | mobileNumber: _mobileNumberTEController.text.trim(), 107 | password: _passwordTEController.text.trim()); 108 | if (mounted && status) { 109 | AppSnackBar().showSnackBar( 110 | title: AppStrings.registrationSuccessTitle, 111 | content: AppStrings.registrationSuccessMessage, 112 | contentType: ContentType.success, 113 | color: AppColor.snackBarSuccessColor, 114 | context: context); 115 | gotoSignIn(); 116 | } 117 | if (mounted && !status) { 118 | AppSnackBar().showSnackBar( 119 | title: AppStrings.registrationFailureTitle, 120 | content: AppStrings.registrationFailureMessage, 121 | contentType: ContentType.failure, 122 | color: AppColor.snackBarFailureColor, 123 | context: context); 124 | } 125 | } 126 | 127 | void gotoSignIn() { 128 | context.read().setPasswordObscure = true; 129 | Navigator.pop(context); 130 | } 131 | 132 | @override 133 | void dispose() { 134 | _emailTEController.dispose(); 135 | _firstNameTEController.dispose(); 136 | _lastNameTEController.dispose(); 137 | _mobileNumberTEController.dispose(); 138 | _passwordTEController.dispose(); 139 | _emailFocusNode.dispose(); 140 | _passwordFocusNode.dispose(); 141 | _firstNameFocusNode.dispose(); 142 | _lastNameFocusNode.dispose(); 143 | _mobileNumberFocusNode.dispose(); 144 | super.dispose(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/views/dashboardScreen/dashboard_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:salomon_bottom_bar/salomon_bottom_bar.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | import 'package:task_manager/services/connectivity_checker.dart'; 6 | import 'package:task_manager/themes/theme_changer.dart'; 7 | import 'package:task_manager/utils/app_assets.dart'; 8 | import 'package:task_manager/utils/app_color.dart'; 9 | import 'package:task_manager/utils/app_strings.dart'; 10 | import 'package:task_manager/viewModels/dashboard_view_model.dart'; 11 | import 'package:task_manager/viewModels/task_view_model.dart'; 12 | import 'package:task_manager/views/taskCancelledScreen/task_cancelled_screen.dart'; 13 | import 'package:task_manager/views/taskCompletedScreen/task_completed_screen.dart'; 14 | import 'package:task_manager/views/taskProgressScreen/task_progress_screen.dart'; 15 | import 'package:task_manager/views/widgets/fallback_widget.dart'; 16 | 17 | import '../newTaskAddScreen/new_task_add_screen.dart'; 18 | import '../widgets/app_bar.dart'; 19 | 20 | class DashboardScreen extends StatefulWidget { 21 | const DashboardScreen({super.key}); 22 | 23 | @override 24 | State createState() => _DashboardScreenState(); 25 | } 26 | 27 | class _DashboardScreenState extends State { 28 | late PageController pageController; 29 | late SharedPreferences? preferences; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | pageController = PageController(); 35 | } 36 | 37 | List screens = const [ 38 | NewTaskAddScreen(), 39 | TaskProgressScreen(), 40 | TaskCompletedScreen(), 41 | TaskCancelledScreen(), 42 | ]; 43 | 44 | int myIndex = 0; 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Scaffold( 49 | appBar: getApplicationAppBar(context: context, disableNavigation: false), 50 | body: Consumer( 51 | builder: (_, viewModel, __) { 52 | if (viewModel.isDeviceConnected) { 53 | return PageView.builder( 54 | onPageChanged: (int value) { 55 | context.read().setIndex = value; 56 | context.read().removeBadgeCount( 57 | value, context.read()); 58 | }, 59 | controller: pageController, 60 | itemCount: screens.length, 61 | itemBuilder: (context, index) { 62 | return screens[index]; 63 | }, 64 | ); 65 | } 66 | return const Column( 67 | children: [ 68 | Expanded( 69 | child: Row( 70 | children: [ 71 | FallbackWidget( 72 | noDataMessage: AppStrings.noInternetText, 73 | asset: AppAssets.noInternet), 74 | ], 75 | ), 76 | ), 77 | ], 78 | ); 79 | }, 80 | ), 81 | bottomNavigationBar: Consumer( 82 | builder: (context, viewModel, child) { 83 | return SalomonBottomBar( 84 | currentIndex: viewModel.index, 85 | onTap: (index) { 86 | if (context.read().isDeviceConnected) { 87 | pageController.jumpToPage(index); 88 | } 89 | viewModel.setIndex = index; 90 | context.read().removeBadgeCount(index, viewModel); 91 | }, 92 | items: [ 93 | SalomonBottomBarItem( 94 | icon: Badge( 95 | backgroundColor: AppColor.appPrimaryColor, 96 | textColor: Colors.white, 97 | isLabelVisible: (context 98 | .read() 99 | .getBadgeCount(AppStrings.taskStatusNew)! > 100 | 0), 101 | label: Padding( 102 | padding: const EdgeInsets.all(2.0), 103 | child: Text(context 104 | .read() 105 | .getBadgeCount(AppStrings.taskStatusNew) 106 | .toString()), 107 | ), 108 | child: const Icon(Icons.add), 109 | ), 110 | title: const Text(AppStrings.taskStatusNew), 111 | selectedColor: AppColor.appPrimaryColor, 112 | unselectedColor: 113 | (context.read().getThemeMode(context) == 114 | ThemeMode.dark) 115 | ? Colors.white 116 | : Colors.black), 117 | SalomonBottomBarItem( 118 | icon: Badge( 119 | backgroundColor: AppColor.appPrimaryColor, 120 | textColor: Colors.white, 121 | isLabelVisible: (context 122 | .read() 123 | .getBadgeCount(AppStrings.taskStatusProgress)! > 124 | 0), 125 | label: Padding( 126 | padding: const EdgeInsets.all(2.0), 127 | child: Text(context 128 | .read() 129 | .getBadgeCount(AppStrings.taskStatusProgress) 130 | .toString()), 131 | ), 132 | child: const Icon(Icons.watch_later_outlined), 133 | ), 134 | title: const Text(AppStrings.taskStatusProgress), 135 | selectedColor: AppColor.appPrimaryColor, 136 | unselectedColor: 137 | (context.read().getThemeMode(context) == 138 | ThemeMode.dark) 139 | ? Colors.white 140 | : Colors.black), 141 | SalomonBottomBarItem( 142 | icon: Badge( 143 | backgroundColor: AppColor.appPrimaryColor, 144 | textColor: Colors.white, 145 | isLabelVisible: (context 146 | .read() 147 | .getBadgeCount(AppStrings.taskStatusCompleted)! > 148 | 0), 149 | label: Padding( 150 | padding: const EdgeInsets.all(2.0), 151 | child: Text(context 152 | .read() 153 | .getBadgeCount(AppStrings.taskStatusCompleted) 154 | .toString()), 155 | ), 156 | child: const Icon(Icons.done_outline_rounded), 157 | ), 158 | title: const Text(AppStrings.taskStatusCompleted), 159 | selectedColor: AppColor.appPrimaryColor, 160 | unselectedColor: 161 | (context.read().getThemeMode(context) == 162 | ThemeMode.dark) 163 | ? Colors.white 164 | : Colors.black), 165 | SalomonBottomBarItem( 166 | icon: Badge( 167 | backgroundColor: AppColor.appPrimaryColor, 168 | textColor: Colors.white, 169 | isLabelVisible: (context 170 | .read() 171 | .getBadgeCount(AppStrings.taskStatusCanceled)! > 172 | 0), 173 | label: Padding( 174 | padding: const EdgeInsets.all(2.0), 175 | child: Text(context 176 | .read() 177 | .getBadgeCount(AppStrings.taskStatusCanceled) 178 | .toString()), 179 | ), 180 | child: const Icon(Icons.cancel_outlined), 181 | ), 182 | title: const Text(AppStrings.taskStatusCanceled), 183 | selectedColor: AppColor.appPrimaryColor, 184 | unselectedColor: 185 | (context.read().getThemeMode(context) == 186 | ThemeMode.dark) 187 | ? Colors.white 188 | : Colors.black), 189 | ], 190 | ); 191 | }, 192 | ), 193 | ); 194 | } 195 | 196 | @override 197 | void dispose() { 198 | pageController.dispose(); 199 | context.read().disableInternetConnectionChecker(); 200 | super.dispose(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /lib/views/newTaskAddScreen/add_task_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:gap/gap.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:task_manager/utils/app_strings.dart'; 6 | import 'package:task_manager/viewModels/task_view_model.dart'; 7 | import 'package:task_manager/viewModels/user_view_model.dart'; 8 | import 'package:task_manager/views/widgets/app_bar.dart'; 9 | import 'package:task_manager/views/widgets/app_snackbar.dart'; 10 | import 'package:task_manager/views/widgets/app_textfield.dart'; 11 | import 'package:task_manager/views/widgets/background_widget.dart'; 12 | 13 | import '../../utils/app_color.dart'; 14 | import '../widgets/circular_progressbar.dart'; 15 | 16 | class AddTaskScreen extends StatefulWidget { 17 | const AddTaskScreen({super.key}); 18 | 19 | @override 20 | State createState() => _AddTaskScreenState(); 21 | } 22 | 23 | class _AddTaskScreenState extends State { 24 | late final FocusNode subjectFocusNode; 25 | late final FocusNode descriptionFocusNode; 26 | late final TextEditingController subjectTEController; 27 | late final TextEditingController descriptionTEController; 28 | late final GlobalKey _formKey; 29 | 30 | @override 31 | void initState() { 32 | subjectTEController = TextEditingController(); 33 | descriptionTEController = TextEditingController(); 34 | subjectFocusNode = FocusNode(); 35 | descriptionFocusNode = FocusNode(); 36 | _formKey = GlobalKey(); 37 | super.initState(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | double screenWidth = MediaQuery.of(context).size.width; 43 | return Scaffold( 44 | appBar: getApplicationAppBar(context: context, disableNavigation: false), 45 | body: OrientationBuilder(builder: (context, orientation) { 46 | return BackgroundWidget( 47 | childWidget: SingleChildScrollView( 48 | child: Container( 49 | margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 60), 50 | child: Column( 51 | crossAxisAlignment: CrossAxisAlignment.start, 52 | children: [ 53 | Text( 54 | AppStrings.addTaskScreenTitle, 55 | style: Theme.of(context).textTheme.headlineLarge, 56 | ), 57 | const Gap(15), 58 | Form( 59 | key: _formKey, 60 | child: Column( 61 | children: [ 62 | AppTextField( 63 | focusNode: subjectFocusNode, 64 | controller: subjectTEController, 65 | inputType: TextInputType.text, 66 | hintText: AppStrings.subjectTextFieldHint, 67 | onFieldSubmitted: (value) { 68 | FocusScope.of(context) 69 | .requestFocus(descriptionFocusNode); 70 | }, 71 | ), 72 | const Gap(15), 73 | AppTextField( 74 | focusNode: descriptionFocusNode, 75 | controller: descriptionTEController, 76 | inputType: TextInputType.multiline, 77 | hintText: AppStrings.descriptionTextFieldHint, 78 | maxLines: 16, 79 | minLines: 10, 80 | onFieldSubmitted: (value) { 81 | FocusScope.of(context).unfocus(); 82 | }, 83 | ), 84 | const Gap(25), 85 | SizedBox( 86 | width: screenWidth * 0.9, 87 | child: Consumer( 88 | builder: (_, viewModel, __) { 89 | return ElevatedButton( 90 | onPressed: () { 91 | if (_formKey.currentState!.validate() && 92 | !context 93 | .read() 94 | .isLoading) { 95 | addTask(); 96 | } 97 | }, 98 | child: viewModel.isLoading 99 | ? const CircularProgressbar( 100 | color: 101 | AppColor.circularProgressbarColor) 102 | : const Icon( 103 | Icons.arrow_circle_right_outlined, 104 | size: 30, 105 | ), 106 | ); 107 | }, 108 | ), 109 | ), 110 | ], 111 | ), 112 | ) 113 | ], 114 | ), 115 | ), 116 | ), 117 | ); 118 | }), 119 | ); 120 | } 121 | 122 | void addTask() async { 123 | bool status = await context.read().createTask( 124 | context.read().token, 125 | subjectTEController.text, 126 | descriptionTEController.text); 127 | if (status && mounted) { 128 | AppSnackBar().showSnackBar( 129 | title: AppStrings.newTaskAddSuccessTitle, 130 | content: AppStrings.newTaskAddSuccessMessage, 131 | contentType: ContentType.success, 132 | color: AppColor.snackBarSuccessColor, 133 | context: context); 134 | Navigator.pop(context, true); 135 | return; 136 | } 137 | if (mounted) { 138 | AppSnackBar().showSnackBar( 139 | title: AppStrings.newTaskAddFailureTitle, 140 | content: AppStrings.newTaskAddFailureMessage, 141 | contentType: ContentType.failure, 142 | color: AppColor.snackBarFailureColor, 143 | context: context); 144 | } 145 | } 146 | 147 | @override 148 | void dispose() { 149 | subjectTEController.dispose(); 150 | subjectFocusNode.dispose(); 151 | descriptionTEController.dispose(); 152 | descriptionFocusNode.dispose(); 153 | super.dispose(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/views/newTaskAddScreen/new_task_add_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:task_manager/services/connectivity_checker.dart'; 5 | import 'package:task_manager/utils/app_color.dart'; 6 | import 'package:task_manager/utils/app_routes.dart'; 7 | import 'package:task_manager/utils/app_strings.dart'; 8 | import 'package:task_manager/views/widgets/fallback_widget.dart'; 9 | import 'package:task_manager/views/widgets/loading_layout.dart'; 10 | import 'package:task_manager/views/widgets/task_list_card.dart'; 11 | import 'package:task_manager/views/widgets/task_status_card.dart'; 12 | 13 | import '../../utils/app_assets.dart'; 14 | import '../../viewModels/task_view_model.dart'; 15 | import '../../viewModels/user_view_model.dart'; 16 | 17 | class NewTaskAddScreen extends StatefulWidget { 18 | const NewTaskAddScreen({super.key}); 19 | 20 | @override 21 | State createState() => _NewTaskAddScreenState(); 22 | } 23 | 24 | class _NewTaskAddScreenState extends State { 25 | AppLifecycleState appLifecycleState = AppLifecycleState.detached; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | double screenWidth = MediaQuery.of(context).size.width; 35 | double screenHeight = MediaQuery.of(context).size.height; 36 | return Scaffold( 37 | body: Container( 38 | height: screenHeight, 39 | margin: const EdgeInsets.all(8.0), 40 | child: RefreshIndicator( 41 | color: AppColor.appPrimaryColor, 42 | onRefresh: () async { 43 | await fetchTasksData(); 44 | }, 45 | child: Column( 46 | crossAxisAlignment: CrossAxisAlignment.start, 47 | children: [ 48 | SingleChildScrollView( 49 | scrollDirection: Axis.horizontal, 50 | child: Consumer( 51 | builder: (_, connectivityViewModel, __) { 52 | if (connectivityViewModel.isDeviceConnected) { 53 | if (context 54 | .read() 55 | .taskDataByStatus[AppStrings.taskStatusNew] == 56 | null) { 57 | fetchTasksData(); 58 | } 59 | return Consumer( 60 | builder: (_, viewModel, __) { 61 | if (viewModel.taskStatusCount.isEmpty) { 62 | return const SizedBox.shrink(); 63 | } 64 | return Row( 65 | children: [ 66 | TaskStatusCard( 67 | screenWidth: screenWidth, 68 | titleText: (viewModel.taskStatusCount[ 69 | AppStrings.taskStatusCanceled] != 70 | "0") 71 | ? viewModel.taskStatusCount[ 72 | AppStrings.taskStatusCanceled] 73 | ?.padLeft(2, "0") ?? 74 | "0" 75 | : "0", 76 | subtitleText: "Canceled"), 77 | TaskStatusCard( 78 | screenWidth: screenWidth, 79 | titleText: (viewModel.taskStatusCount[ 80 | AppStrings.taskStatusCompleted] != 81 | "0") 82 | ? viewModel.taskStatusCount[AppStrings 83 | .taskStatusCompleted] 84 | ?.padLeft(2, "0") ?? 85 | "0" 86 | : "0", 87 | subtitleText: AppStrings.taskStatusCompleted), 88 | TaskStatusCard( 89 | screenWidth: screenWidth, 90 | titleText: (viewModel.taskStatusCount[ 91 | AppStrings.taskStatusProgress] != 92 | "0") 93 | ? viewModel.taskStatusCount[ 94 | AppStrings.taskStatusProgress] 95 | ?.padLeft(2, "0") ?? 96 | "0" 97 | : "0", 98 | subtitleText: AppStrings.taskStatusProgress), 99 | TaskStatusCard( 100 | screenWidth: screenWidth, 101 | titleText: (viewModel.taskStatusCount[ 102 | AppStrings.taskStatusNew] != 103 | "0") 104 | ? viewModel.taskStatusCount[ 105 | AppStrings.taskStatusNew] 106 | ?.padLeft(2, "0") ?? 107 | "0" 108 | : "0", 109 | subtitleText: AppStrings.taskStatusNew), 110 | ], 111 | ); 112 | }, 113 | ); 114 | } 115 | return const SizedBox.shrink(); 116 | }, 117 | ), 118 | ), 119 | const Gap(5), 120 | Consumer(builder: (_, viewModel, __) { 121 | if (viewModel.taskDataByStatus[AppStrings.taskStatusNew] == 122 | null) { 123 | return const LoadingLayout(); 124 | } 125 | if (viewModel 126 | .taskDataByStatus[AppStrings.taskStatusNew]!.isEmpty) { 127 | return const FallbackWidget( 128 | noDataMessage: AppStrings.noNewTaskData, 129 | asset: AppAssets.emptyList); 130 | } 131 | return TaskListCard( 132 | screenWidth: screenWidth, 133 | taskData: 134 | viewModel.taskDataByStatus[AppStrings.taskStatusNew]!, 135 | chipColor: AppColor.newTaskChipColor, 136 | currentScreen: AppStrings.taskStatusNew, 137 | ); 138 | }), 139 | ], 140 | ), 141 | ), 142 | ), 143 | floatingActionButton: FloatingActionButton( 144 | onPressed: () { 145 | Navigator.pushNamed(context, AppRoutes.addTaskScreen); 146 | }, 147 | backgroundColor: AppColor.appPrimaryColor, 148 | child: const Icon(Icons.add, size: 27), 149 | //params 150 | ), 151 | ); 152 | } 153 | 154 | Future fetchTasksData() async { 155 | if (mounted) { 156 | await context 157 | .read() 158 | .fetchTaskStatusData(context.read().token); 159 | } 160 | if (mounted) { 161 | await context 162 | .read() 163 | .fetchTaskList(context.read().token); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lib/views/splashScreen/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | import 'package:task_manager/utils/app_assets.dart'; 6 | import 'package:task_manager/utils/app_routes.dart'; 7 | import 'package:task_manager/viewModels/auth_view_model.dart'; 8 | import 'package:task_manager/viewModels/user_view_model.dart'; 9 | import 'package:task_manager/wrappers/svg_image_loader.dart'; 10 | import 'package:task_manager/wrappers/widget_custom_animator.dart'; 11 | import 'package:widget_and_text_animator/widget_and_text_animator.dart'; 12 | 13 | import '../widgets/background_widget.dart'; 14 | 15 | class SplashScreen extends StatefulWidget { 16 | const SplashScreen({super.key}); 17 | 18 | @override 19 | State createState() => _SplashScreenState(); 20 | } 21 | 22 | class _SplashScreenState extends State { 23 | @override 24 | void initState() { 25 | super.initState(); 26 | checkToken(); 27 | } 28 | 29 | Future checkToken() async { 30 | try { 31 | SharedPreferences preferences = await SharedPreferences.getInstance(); 32 | String? token = preferences.getString("token"); 33 | if (mounted) { 34 | bool status = 35 | await context.read().authenticateToken(token); 36 | if (status && mounted) { 37 | await context.read().loadUserData(preferences); 38 | Future.delayed(const Duration(seconds: 2), () { 39 | Navigator.pushReplacementNamed(context, AppRoutes.dashboardScreen); 40 | }); 41 | } else { 42 | Future.delayed(const Duration(seconds: 3), () { 43 | Navigator.pushReplacementNamed(context, AppRoutes.signInScreen); 44 | }); 45 | } 46 | } 47 | } catch (exception) { 48 | if (kDebugMode) { 49 | debugPrint(exception.toString()); 50 | } 51 | } 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Scaffold( 57 | body: BackgroundWidget( 58 | childWidget: Center( 59 | child: WidgetCustomAnimator( 60 | incomingEffect: WidgetTransitionEffects.outgoingSlideOutToBottom( 61 | duration: const Duration(seconds: 2), scale: 0.3), 62 | childWidget: const SVGImageLoader( 63 | asset: AppAssets.logo, 64 | fit: BoxFit.cover, 65 | ), 66 | ), 67 | ), 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/views/taskCancelledScreen/task_cancelled_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../../utils/app_assets.dart'; 6 | import '../../utils/app_color.dart'; 7 | import '../../utils/app_strings.dart'; 8 | import '../../viewModels/task_view_model.dart'; 9 | import '../../viewModels/user_view_model.dart'; 10 | import '../widgets/fallback_widget.dart'; 11 | import '../widgets/loading_layout.dart'; 12 | import '../widgets/task_list_card.dart'; 13 | 14 | class TaskCancelledScreen extends StatefulWidget { 15 | const TaskCancelledScreen({super.key}); 16 | 17 | @override 18 | State createState() => _TaskCancelledScreenState(); 19 | } 20 | 21 | class _TaskCancelledScreenState extends State { 22 | @override 23 | Widget build(BuildContext context) { 24 | double screenWidth = MediaQuery.of(context).size.width; 25 | return Scaffold( 26 | body: Container( 27 | margin: const EdgeInsets.all(8), 28 | child: RefreshIndicator( 29 | color: AppColor.appPrimaryColor, 30 | onRefresh: () async { 31 | await fetchListData(); 32 | }, 33 | child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | const Gap(5), 37 | Consumer(builder: (_, viewModel, __) { 38 | if (viewModel.taskDataByStatus[AppStrings.taskStatusCanceled] == 39 | null) { 40 | return const LoadingLayout(); 41 | } 42 | if (viewModel 43 | .taskDataByStatus[AppStrings.taskStatusCanceled]!.isEmpty) { 44 | return const FallbackWidget( 45 | noDataMessage: AppStrings.noNewCanceledData, 46 | asset: AppAssets.emptyList, 47 | ); 48 | } 49 | return TaskListCard( 50 | screenWidth: screenWidth, 51 | taskData: viewModel 52 | .taskDataByStatus[AppStrings.taskStatusCanceled]!, 53 | chipColor: AppColor.canceledChipColor, 54 | currentScreen: AppStrings.taskStatusCanceled, 55 | ); 56 | }) 57 | ], 58 | ), 59 | ), 60 | ), 61 | ); 62 | } 63 | 64 | Future fetchListData() async { 65 | await context 66 | .read() 67 | .fetchTaskList(context.read().token); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/views/taskCompletedScreen/task_completed_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../../utils/app_assets.dart'; 6 | import '../../utils/app_color.dart'; 7 | import '../../utils/app_strings.dart'; 8 | import '../../viewModels/task_view_model.dart'; 9 | import '../../viewModels/user_view_model.dart'; 10 | import '../widgets/fallback_widget.dart'; 11 | import '../widgets/loading_layout.dart'; 12 | import '../widgets/task_list_card.dart'; 13 | 14 | class TaskCompletedScreen extends StatefulWidget { 15 | const TaskCompletedScreen({super.key}); 16 | 17 | @override 18 | State createState() => _TaskCompletedScreenState(); 19 | } 20 | 21 | class _TaskCompletedScreenState extends State { 22 | @override 23 | Widget build(BuildContext context) { 24 | double screenWidth = MediaQuery.of(context).size.width; 25 | return Scaffold( 26 | body: Container( 27 | margin: const EdgeInsets.all(8), 28 | child: RefreshIndicator( 29 | color: AppColor.appPrimaryColor, 30 | onRefresh: () async { 31 | await fetchListData(); 32 | }, 33 | child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | const Gap(5), 37 | Consumer(builder: (_, viewModel, __) { 38 | if (viewModel 39 | .taskDataByStatus[AppStrings.taskStatusCompleted] == 40 | null) { 41 | return const LoadingLayout(); 42 | } 43 | if (viewModel.taskDataByStatus[AppStrings.taskStatusCompleted]! 44 | .isEmpty) { 45 | return const FallbackWidget( 46 | noDataMessage: AppStrings.noCompletedTaskData, 47 | asset: AppAssets.emptyList, 48 | ); 49 | } 50 | return TaskListCard( 51 | screenWidth: screenWidth, 52 | taskData: viewModel 53 | .taskDataByStatus[AppStrings.taskStatusCompleted]!, 54 | chipColor: AppColor.completedChipColor, 55 | currentScreen: AppStrings.taskStatusCompleted, 56 | ); 57 | }) 58 | ], 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | 65 | Future fetchListData() async { 66 | await context 67 | .read() 68 | .fetchTaskList(context.read().token); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/views/taskProgressScreen/task_progress_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../../utils/app_assets.dart'; 6 | import '../../utils/app_color.dart'; 7 | import '../../utils/app_strings.dart'; 8 | import '../../viewModels/task_view_model.dart'; 9 | import '../../viewModels/user_view_model.dart'; 10 | import '../widgets/fallback_widget.dart'; 11 | import '../widgets/loading_layout.dart'; 12 | import '../widgets/task_list_card.dart'; 13 | 14 | class TaskProgressScreen extends StatefulWidget { 15 | const TaskProgressScreen({super.key}); 16 | 17 | @override 18 | State createState() => _TaskProgressScreenState(); 19 | } 20 | 21 | class _TaskProgressScreenState extends State { 22 | @override 23 | Widget build(BuildContext context) { 24 | double screenWidth = MediaQuery.of(context).size.width; 25 | return Scaffold( 26 | body: Container( 27 | margin: const EdgeInsets.all(8), 28 | child: RefreshIndicator( 29 | color: AppColor.appPrimaryColor, 30 | onRefresh: () async { 31 | await fetchListData(); 32 | }, 33 | child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | const Gap(5), 37 | Consumer(builder: (_, viewModel, __) { 38 | if (viewModel.taskDataByStatus[AppStrings.taskStatusProgress] == 39 | null) { 40 | return const LoadingLayout(); 41 | } 42 | if (viewModel 43 | .taskDataByStatus[AppStrings.taskStatusProgress]!.isEmpty) { 44 | return const FallbackWidget( 45 | noDataMessage: AppStrings.noProgressTaskData, 46 | asset: AppAssets.emptyList, 47 | ); 48 | } 49 | return TaskListCard( 50 | screenWidth: screenWidth, 51 | taskData: viewModel 52 | .taskDataByStatus[AppStrings.taskStatusProgress]!, 53 | chipColor: AppColor.progressChipColor, 54 | currentScreen: AppStrings.taskStatusProgress, 55 | ); 56 | }) 57 | ], 58 | ), 59 | ), 60 | ), 61 | ); 62 | } 63 | 64 | Future fetchListData() async { 65 | await context 66 | .read() 67 | .fetchTaskList(context.read().token); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/views/updateProfileScreen/update_profile_screen_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../../utils/app_color.dart'; 6 | import '../../utils/app_strings.dart'; 7 | import '../../viewModels/user_view_model.dart'; 8 | import '../widgets/app_textfield.dart'; 9 | import '../widgets/circular_progressbar.dart'; 10 | 11 | class UpdateProfileScreenForm extends StatelessWidget { 12 | final TextEditingController emailTEController, 13 | firstNameTEController, 14 | lastNameTEController, 15 | mobileNumberTEController, 16 | passwordTEController; 17 | final GlobalKey formKey; 18 | final FocusNode emailFocusNode, 19 | passwordFocusNode, 20 | firstNameFocusNode, 21 | lastNameFocusNode, 22 | mobileNumberFocusNode; 23 | final Function(UserViewModel viewModel) onPressed; 24 | 25 | const UpdateProfileScreenForm( 26 | {super.key, 27 | required this.emailTEController, 28 | required this.firstNameTEController, 29 | required this.lastNameTEController, 30 | required this.mobileNumberTEController, 31 | required this.passwordTEController, 32 | required this.formKey, 33 | required this.emailFocusNode, 34 | required this.passwordFocusNode, 35 | required this.firstNameFocusNode, 36 | required this.lastNameFocusNode, 37 | required this.mobileNumberFocusNode, 38 | required this.onPressed}); 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Form( 43 | key: formKey, 44 | child: Column( 45 | children: [ 46 | AppTextField( 47 | focusNode: emailFocusNode, 48 | controller: emailTEController, 49 | inputType: TextInputType.emailAddress, 50 | hintText: AppStrings.emailTextFieldHint, 51 | errorText: AppStrings.emailErrorText, 52 | regEx: AppStrings.emailRegEx, 53 | onFieldSubmitted: (value) { 54 | FocusScope.of(context).requestFocus(firstNameFocusNode); 55 | }, 56 | labelText: AppStrings.emailTextFieldHint, 57 | ), 58 | const Gap(20), 59 | AppTextField( 60 | focusNode: firstNameFocusNode, 61 | controller: firstNameTEController, 62 | inputType: TextInputType.text, 63 | hintText: AppStrings.firstNameTextFieldHint, 64 | errorText: AppStrings.firstNameErrorText, 65 | onFieldSubmitted: (value) { 66 | FocusScope.of(context).requestFocus(lastNameFocusNode); 67 | }, 68 | labelText: AppStrings.firstNameTextFieldHint, 69 | ), 70 | const Gap(20), 71 | AppTextField( 72 | focusNode: lastNameFocusNode, 73 | controller: lastNameTEController, 74 | inputType: TextInputType.text, 75 | hintText: AppStrings.lastNameTextFieldHint, 76 | errorText: AppStrings.lastNameErrorText, 77 | onFieldSubmitted: (value) { 78 | FocusScope.of(context).requestFocus(mobileNumberFocusNode); 79 | }, 80 | labelText: AppStrings.lastNameTextFieldHint, 81 | ), 82 | const Gap(20), 83 | AppTextField( 84 | focusNode: mobileNumberFocusNode, 85 | controller: mobileNumberTEController, 86 | inputType: TextInputType.number, 87 | hintText: AppStrings.mobileNumberTextFieldHint, 88 | errorText: AppStrings.mobileNumberErrorText, 89 | regEx: AppStrings.phoneNumberRegEx, 90 | maxLength: 14, 91 | onFieldSubmitted: (value) { 92 | FocusScope.of(context).requestFocus(passwordFocusNode); 93 | }, 94 | labelText: AppStrings.mobileNumberTextFieldHint, 95 | ), 96 | const Gap(20), 97 | Consumer( 98 | builder: (_, viewModel, __) => AppTextField( 99 | focusNode: passwordFocusNode, 100 | controller: passwordTEController, 101 | isObscureText: viewModel.isPasswordObscured, 102 | suffixIcon: InkWell( 103 | splashColor: Colors.transparent, 104 | onTap: () { 105 | viewModel.setIsPasswordObscured = 106 | !viewModel.isPasswordObscured; 107 | }, 108 | child: (viewModel.isPasswordObscured) 109 | ? const Icon(Icons.visibility, 110 | color: AppColor.appPrimaryColor) 111 | : const Icon(Icons.visibility_off, 112 | color: AppColor.appPrimaryColor), 113 | ), 114 | inputType: TextInputType.text, 115 | hintText: AppStrings.passwordTextFieldHint, 116 | errorText: AppStrings.passwordErrorText, 117 | onFieldSubmitted: (value) { 118 | FocusScope.of(context).unfocus(); 119 | }, 120 | labelText: AppStrings.passwordTextFieldHint, 121 | ), 122 | ), 123 | const Gap(20), 124 | SizedBox( 125 | width: MediaQuery.of(context).size.width * 0.9, 126 | child: Consumer( 127 | builder: (_, viewModel, __) { 128 | return ElevatedButton( 129 | onPressed: () => onPressed(viewModel), 130 | child: viewModel.isLoading 131 | ? const CircularProgressbar( 132 | color: AppColor.circularProgressbarColor) 133 | : const Icon(Icons.arrow_circle_right_outlined, size: 30), 134 | ); 135 | }, 136 | ), 137 | ), 138 | ], 139 | ), 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/views/widgets/app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:gap/gap.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | import 'package:task_manager/themes/theme_changer.dart'; 8 | import 'package:task_manager/utils/app_routes.dart'; 9 | import 'package:task_manager/viewModels/auth_view_model.dart'; 10 | 11 | import '../../utils/app_assets.dart'; 12 | import '../../viewModels/user_view_model.dart'; 13 | 14 | AppBar getApplicationAppBar( 15 | {required BuildContext context, 16 | required bool disableNavigation, 17 | SharedPreferences? preference}) { 18 | return AppBar( 19 | automaticallyImplyLeading: false, 20 | title: Consumer( 21 | builder: (_, viewModel, __) { 22 | return Row( 23 | children: [ 24 | InkWell( 25 | onTap: () { 26 | if (!disableNavigation) { 27 | Navigator.pushNamed(context, AppRoutes.updateProfileScreen) 28 | .then((value) { 29 | context.read().base64Image = ""; 30 | context.read().imageName = ""; 31 | }); 32 | } 33 | }, 34 | child: CircleAvatar( 35 | radius: 20, 36 | backgroundColor: Colors.white, 37 | backgroundImage: (viewModel.userData.photo!.isEmpty) 38 | ? const AssetImage(AppAssets.userDefaultImage) 39 | : MemoryImage( 40 | base64Decode( 41 | viewModel.userData.photo.toString(), 42 | ), 43 | ), 44 | ), 45 | ), 46 | const Gap(10), 47 | Column( 48 | crossAxisAlignment: CrossAxisAlignment.start, 49 | children: [ 50 | Text( 51 | "${viewModel.userData.firstName} ${viewModel.userData.lastName}", 52 | style: Theme.of(context).textTheme.labelMedium, 53 | ), 54 | Text( 55 | viewModel.userData.email.toString(), 56 | style: Theme.of(context).textTheme.labelSmall, 57 | ) 58 | ], 59 | ) 60 | ], 61 | ); 62 | }, 63 | ), 64 | actions: [ 65 | IconButton( 66 | onPressed: () { 67 | if (context.read().getThemeMode(context) == 68 | ThemeMode.dark) { 69 | context.read().setThemeMode = ThemeMode.light; 70 | saveThemeData("light"); 71 | print("changed"); 72 | return; 73 | } 74 | if (context.read().getThemeMode(context) == 75 | ThemeMode.dark) { 76 | context.read().setThemeMode = ThemeMode.light; 77 | saveThemeData("light"); 78 | return; 79 | } 80 | context.read().setThemeMode = ThemeMode.dark; 81 | saveThemeData("dark"); 82 | }, 83 | splashColor: Colors.transparent, 84 | icon: Icon((context.read().getThemeMode(context) == 85 | ThemeMode.dark) 86 | ? Icons.light_mode_outlined 87 | : Icons.dark_mode_outlined), 88 | ), 89 | IconButton( 90 | onPressed: () async { 91 | await context.read().signOut(); 92 | }, 93 | icon: const Icon(Icons.logout), 94 | ), 95 | ], 96 | ); 97 | } 98 | 99 | void saveThemeData(String theme) async { 100 | SharedPreferences preferences = await SharedPreferences.getInstance(); 101 | preferences.setString("themeMode", theme); 102 | } 103 | -------------------------------------------------------------------------------- /lib/views/widgets/app_snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:awesome_snackbar_content/awesome_snackbar_content.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:task_manager/wrappers/widget_custom_animator.dart'; 4 | import 'package:widget_and_text_animator/widget_and_text_animator.dart'; 5 | 6 | class AppSnackBar { 7 | static AppSnackBar? _instance; 8 | 9 | AppSnackBar._(); 10 | 11 | factory AppSnackBar() { 12 | return _instance ??= AppSnackBar._(); 13 | } 14 | 15 | void showSnackBar({ 16 | required String title, 17 | required String content, 18 | required ContentType contentType, 19 | required Color color, 20 | required BuildContext context, 21 | }) { 22 | ScaffoldMessenger.of(context) 23 | ..clearSnackBars() 24 | ..showSnackBar(getSnackBar( 25 | title: title, 26 | content: content, 27 | contentType: contentType, 28 | color: color)); 29 | } 30 | } 31 | 32 | SnackBar getSnackBar( 33 | {required String title, 34 | required String content, 35 | required ContentType contentType, 36 | required Color color}) { 37 | return SnackBar( 38 | elevation: 0, 39 | behavior: SnackBarBehavior.floating, 40 | backgroundColor: Colors.transparent, 41 | dismissDirection: DismissDirection.down, 42 | content: WidgetCustomAnimator( 43 | incomingEffect: WidgetTransitionEffects.incomingSlideInFromBottom( 44 | duration: const Duration(seconds: 1), scale: 0.4, opacity: 0.1), 45 | childWidget: AwesomeSnackbarContent( 46 | title: title, 47 | titleFontSize: 16, 48 | messageFontSize: 13, 49 | message: content, 50 | color: color, 51 | contentType: contentType, 52 | ), 53 | ), 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /lib/views/widgets/app_textfield.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:task_manager/utils/app_color.dart'; 3 | 4 | import '../../utils/app_strings.dart'; 5 | 6 | class AppTextField extends StatelessWidget { 7 | final FocusNode focusNode; 8 | final bool isObscureText; 9 | final TextEditingController controller; 10 | final Widget? suffixIcon; 11 | final Function(String)? onFieldSubmitted; 12 | final String hintText, errorText, regEx; 13 | final TextInputType inputType; 14 | final int? maxLength; 15 | final TextAlign? textAlign; 16 | final TextStyle? textStyle; 17 | final bool disableValidation, setCustomValidation; 18 | final Function(String value)? onChanged, customValidation; 19 | final OutlineInputBorder? outlineInputBorder; 20 | final bool expands; 21 | final int? maxLines, minLines; 22 | final String? labelText; 23 | 24 | const AppTextField({ 25 | super.key, 26 | required this.focusNode, 27 | this.isObscureText = false, 28 | required this.controller, 29 | this.suffixIcon, 30 | this.onFieldSubmitted, 31 | this.hintText = "", 32 | required this.inputType, 33 | this.errorText = "", 34 | this.regEx = "", 35 | this.maxLength, 36 | this.onChanged, 37 | this.labelText, 38 | this.setCustomValidation = false, 39 | this.customValidation, 40 | this.disableValidation = false, 41 | this.textStyle, 42 | this.textAlign, 43 | this.maxLines = 1, 44 | this.minLines = 1, 45 | this.outlineInputBorder, 46 | this.expands = false, 47 | }); 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return TextFormField( 52 | textAlign: textAlign ?? TextAlign.start, 53 | keyboardType: inputType, 54 | style: textStyle, 55 | focusNode: focusNode, 56 | autofocus: false, 57 | controller: controller, 58 | maxLength: maxLength, 59 | maxLines: maxLines, 60 | minLines: minLines, 61 | obscureText: isObscureText, 62 | expands: expands, 63 | obscuringCharacter: AppStrings.obscuringChar, 64 | cursorColor: AppColor.appPrimaryColor, 65 | decoration: InputDecoration( 66 | hintText: hintText, 67 | labelText: labelText, 68 | suffixIcon: suffixIcon, 69 | counterText: "", 70 | focusedBorder: outlineInputBorder), 71 | onFieldSubmitted: onFieldSubmitted, 72 | onChanged: onChanged, 73 | onTapOutside: (value) { 74 | FocusScope.of(context).unfocus(); 75 | }, 76 | validator: (value) { 77 | if (disableValidation) { 78 | return null; 79 | } 80 | if (setCustomValidation) { 81 | return customValidation!(value.toString()); 82 | } 83 | if (value!.isEmpty || 84 | (regEx.isNotEmpty && !RegExp(regEx).hasMatch(value))) { 85 | return errorText; 86 | } 87 | return null; 88 | }, 89 | autovalidateMode: (!disableValidation) 90 | ? AutovalidateMode.onUserInteraction 91 | : AutovalidateMode.disabled, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/views/widgets/background_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:task_manager/themes/theme_changer.dart'; 4 | 5 | import '../../wrappers/svg_image_loader.dart'; 6 | 7 | class BackgroundWidget extends StatelessWidget { 8 | final Widget childWidget; 9 | 10 | const BackgroundWidget({super.key, required this.childWidget}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return SafeArea( 15 | child: Stack( 16 | children: [ 17 | SizedBox( 18 | width: double.infinity, 19 | child: Consumer( 20 | builder: (_, viewModel, __) { 21 | return SVGImageLoader( 22 | asset: viewModel.getBackgroundImage(context), 23 | fit: BoxFit.cover, 24 | height: MediaQuery.of(context).size.height, 25 | ); 26 | }, 27 | ), 28 | ), 29 | childWidget, 30 | ], 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/views/widgets/circular_progressbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CircularProgressbar extends StatelessWidget { 4 | final Color color; 5 | final double height, width; 6 | 7 | const CircularProgressbar({ 8 | super.key, 9 | required this.color, 10 | this.height = 30, 11 | this.width = 30, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SizedBox( 17 | height: height, 18 | width: width, 19 | child: CircularProgressIndicator( 20 | color: color, 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/views/widgets/fallback_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | 4 | import '../../wrappers/svg_image_loader.dart'; 5 | 6 | class FallbackWidget extends StatelessWidget { 7 | final String noDataMessage; 8 | final String asset; 9 | 10 | const FallbackWidget( 11 | {super.key, required this.noDataMessage, required this.asset}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Expanded( 16 | child: SingleChildScrollView( 17 | child: Column( 18 | mainAxisAlignment: MainAxisAlignment.center, 19 | children: [ 20 | Gap(MediaQuery.of(context).size.height * 0.15), 21 | SizedBox( 22 | height: MediaQuery.of(context).size.height * 0.35, 23 | child: SVGImageLoader(asset: asset, fit: BoxFit.contain), 24 | ), 25 | const Gap(20), 26 | Padding( 27 | padding: const EdgeInsets.symmetric(horizontal: 10), 28 | child: Text( 29 | textAlign: TextAlign.center, 30 | noDataMessage, 31 | style: Theme.of(context).textTheme.titleMedium, 32 | ), 33 | ), 34 | ], 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/views/widgets/forget_password_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:task_manager/views/widgets/background_widget.dart'; 5 | import 'package:task_manager/views/widgets/sign_in_bottom_text.dart'; 6 | 7 | import '../../utils/app_color.dart'; 8 | import '../../viewModels/auth_view_model.dart'; 9 | import 'circular_progressbar.dart'; 10 | 11 | class ForgetPasswordLayout extends StatelessWidget { 12 | final double horizontalMargin, verticalMargin; 13 | final double screenWidth; 14 | final Orientation orientation; 15 | final Widget child, buttonWidget; 16 | final String headerText, bodyText; 17 | final Function(AuthViewModel viewModel) onButtonPressed; 18 | 19 | const ForgetPasswordLayout( 20 | {super.key, 21 | required this.orientation, 22 | required this.child, 23 | required this.horizontalMargin, 24 | required this.verticalMargin, 25 | required this.headerText, 26 | required this.bodyText, 27 | required this.screenWidth, 28 | required this.buttonWidget, 29 | required this.onButtonPressed}); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return BackgroundWidget( 34 | childWidget: SingleChildScrollView( 35 | child: Container( 36 | margin: EdgeInsets.symmetric( 37 | horizontal: horizontalMargin, 38 | vertical: verticalMargin, 39 | ), 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.start, 42 | children: [ 43 | Text( 44 | headerText, 45 | style: Theme.of(context).textTheme.headlineLarge, 46 | ), 47 | const Gap(5), 48 | Text( 49 | bodyText, 50 | style: Theme.of(context).textTheme.bodySmall, 51 | ), 52 | const Gap(20), 53 | child, 54 | const Gap(20), 55 | SizedBox( 56 | width: screenWidth * 0.9, 57 | child: Consumer( 58 | builder: (_, viewModel, __) { 59 | return ElevatedButton( 60 | onPressed: () => onButtonPressed(viewModel), 61 | child: viewModel.isLoading 62 | ? const CircularProgressbar( 63 | color: AppColor.circularProgressbarColor) 64 | : buttonWidget); 65 | }, 66 | )), 67 | const Gap(30), 68 | SignInBottomText(route: () { 69 | Navigator.pop(context); 70 | }) 71 | ], 72 | ), 73 | ), 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/views/widgets/loading_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../utils/app_color.dart'; 4 | 5 | class LoadingLayout extends StatelessWidget { 6 | const LoadingLayout({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return const Expanded( 11 | child: Center( 12 | child: CircularProgressIndicator( 13 | color: AppColor.appPrimaryColor, 14 | ), 15 | ), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/views/widgets/sign_in_bottom_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../utils/app_color.dart'; 5 | import '../../utils/app_strings.dart'; 6 | 7 | class SignInBottomText extends StatelessWidget { 8 | final Function route; 9 | 10 | const SignInBottomText({super.key, required this.route}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Center( 15 | child: RichText( 16 | text: TextSpan( 17 | text: AppStrings.signUpBottomTextOne, 18 | style: Theme.of(context).textTheme.bodyMedium, 19 | children: [ 20 | TextSpan( 21 | text: AppStrings.signUpBottomTextTwo, 22 | recognizer: TapGestureRecognizer()..onTap = () => route(), 23 | style: const TextStyle( 24 | color: AppColor.appPrimaryColor, 25 | ), 26 | ), 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/views/widgets/task_status_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TaskStatusCard extends StatelessWidget { 4 | final double screenWidth; 5 | final String titleText; 6 | final String subtitleText; 7 | 8 | const TaskStatusCard( 9 | {super.key, 10 | required this.screenWidth, 11 | required this.titleText, 12 | required this.subtitleText}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SizedBox( 17 | width: screenWidth * 0.31, 18 | child: Card( 19 | child: Padding( 20 | padding: const EdgeInsets.all(10), 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Text( 25 | titleText, 26 | style: Theme.of(context).textTheme.titleLarge, 27 | ), 28 | Text( 29 | subtitleText, 30 | style: Theme.of(context).textTheme.bodySmall, 31 | ), 32 | ], 33 | ), 34 | ), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/wrappers/svg_image_loader.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | 4 | class SVGImageLoader extends StatelessWidget { 5 | final String asset; 6 | final BoxFit fit; 7 | final double? height, width; 8 | 9 | const SVGImageLoader( 10 | {super.key, 11 | required this.asset, 12 | required this.fit, 13 | this.height, 14 | this.width}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return SvgPicture.asset( 19 | asset, 20 | fit: fit, 21 | height: height, 22 | width: width, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/wrappers/widget_custom_animator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:widget_and_text_animator/widget_and_text_animator.dart'; 3 | 4 | class WidgetCustomAnimator extends StatelessWidget { 5 | final Widget childWidget; 6 | final WidgetTransitionEffects? incomingEffect; 7 | final WidgetTransitionEffects? outGoingEffect; 8 | final WidgetRestingEffects? atRestEffect; 9 | 10 | const WidgetCustomAnimator({ 11 | super.key, 12 | required this.childWidget, 13 | this.incomingEffect, 14 | this.outGoingEffect, 15 | this.atRestEffect, 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return WidgetAnimator( 21 | incomingEffect: incomingEffect, 22 | outgoingEffect: outGoingEffect, 23 | atRestEffect: atRestEffect, 24 | child: childWidget, 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: task_manager 2 | description: "A new Flutter task manager project." 3 | 4 | publish_to: 'none' 5 | version: 1.1.0+1 6 | 7 | environment: 8 | sdk: '>=3.4.1 <4.0.0' 9 | 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | 16 | 17 | cupertino_icons: ^1.0.6 18 | flutter_svg: ^2.0.10+1 19 | widget_and_text_animator: ^1.1.5 20 | gap: ^3.0.1 21 | provider: ^6.1.2 22 | http: ^1.2.1 23 | awesome_snackbar_content: ^0.1.3 24 | shared_preferences: ^2.2.3 25 | jwt_decoder: ^2.0.1 26 | ionicons: ^0.2.2 27 | salomon_bottom_bar: ^3.3.2 28 | image_picker: ^1.1.2 29 | internet_connection_checker: ^1.0.0+1 30 | device_preview: ^1.2.0 31 | 32 | dev_dependencies: 33 | flutter_test: 34 | sdk: flutter 35 | 36 | 37 | flutter_lints: ^3.0.0 38 | 39 | flutter: 40 | 41 | 42 | uses-material-design: true 43 | 44 | assets: 45 | - assets/images/ 46 | 47 | fonts: 48 | - family: Poppins 49 | fonts: 50 | - asset: assets/fonts/Poppins-Regular.ttf 51 | - family: Poppins Bold 52 | fonts: 53 | - asset: assets/fonts/Poppins-Bold.ttf 54 | # style: italic 55 | # - family: Trajan Pro 56 | # fonts: 57 | # - asset: fonts/TrajanPro.ttf 58 | # - asset: fonts/TrajanPro_Bold.ttf 59 | # weight: 700 60 | # 61 | # For details regarding fonts from package dependencies, 62 | # see https://flutter.dev/custom-fonts/#from-packages 63 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:task_manager/app/app.dart'; 11 | 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const TaskManager(userTheme: 'system',)); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------