├── .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 |
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 |
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 |
59 |
60 |
61 |
62 |
63 | `Dark Mode`
64 |
65 |
66 |
67 |
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 |
78 |
79 |
80 |
81 |
82 | `Dark Mode`
83 |
84 |
85 |
86 |
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 |
99 |
100 |
101 |
102 |
103 |
104 | `Dark Mode`
105 |
106 |
107 |
108 |
109 |
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 |
119 |
120 |
121 |
122 | `Dark Mode`
123 |
124 |
125 |
126 |
127 |
128 | `4. Internet connection observer`
129 | > Users will get notified if theres any internet issue.
130 |
131 |
132 |
133 |
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 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
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 |
--------------------------------------------------------------------------------