├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .metadata ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ ├── coolbeatz71 │ │ │ │ └── todo_app │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── example │ │ │ │ └── todo_app │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ ├── app_icon.png │ │ │ └── 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 │ │ │ ├── raw │ │ │ ├── keep.xml │ │ │ └── wecker_sound.mp3 │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── assets └── images │ ├── dont-know.gif │ ├── to-do-list.svg │ ├── undraw_ideas.svg │ └── undraw_no_data.svg ├── fonts ├── OpenSans-Bold.ttf ├── OpenSans-ExtraBold.ttf ├── OpenSans-Light.ttf ├── OpenSans-Regular.ttf └── OpenSans-SemiBold.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── flutter_export_environment.sh ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── 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 └── wecker_sound.aiff ├── lib ├── bloc │ ├── task_bloc.dart │ ├── task_event.dart │ └── task_state.dart ├── core │ ├── custom_switch.dart │ ├── dialog.dart │ ├── flushbar.dart │ └── validation.dart ├── helpers │ ├── animation.dart │ ├── colors.dart │ └── utils.dart ├── main.dart ├── models │ └── task.dart ├── routes │ ├── router.dart │ └── router.gr.dart ├── services │ ├── auth_key.dart │ ├── firestore.dart │ └── notification.dart └── views │ ├── pages │ ├── details │ │ ├── completion_task.dart │ │ ├── details.dart │ │ ├── reminder_status.dart │ │ ├── task_icon.dart │ │ ├── task_list.dart │ │ └── task_list_tile.dart │ ├── home │ │ ├── bottomsheet.dart │ │ ├── btn_create_task.dart │ │ └── home.dart │ ├── profile │ │ ├── active_task │ │ │ └── active_task.dart │ │ ├── all_task │ │ │ └── all_task.page.dart │ │ ├── completed_task │ │ │ └── completed_task.dart │ │ └── profile.dart │ └── wrapper.dart │ └── widgets │ ├── add_task_fb.dart │ ├── date_picker_timeline │ ├── core │ │ ├── dimen.dart │ │ └── style.dart │ ├── date_picker_timeline.dart │ ├── date_widget.dart │ └── gestures │ │ └── tap.dart │ ├── day_task.dart │ ├── forms │ └── task_form.dart │ ├── illustration.dart │ ├── no_data_illustration.dart │ ├── task_card │ ├── card_color.dart │ ├── custom_painter.dart │ ├── task_card.dart │ ├── text_info.dart │ └── time_board.dart │ └── task_list.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Description 2 | 3 | #### Type of change 4 | 5 | Please select the relevant option 6 | 7 | - [ ] Bug fix (non-breaking change which fixes an issue) 8 | - [ ] New feature (non-breaking change which adds functionality) 9 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 10 | - [ ] Others (cosmetics, styling, improvements) 11 | - [ ] This change requires a documentation update 12 | 13 | #### How Has This Been Tested? 14 | 15 | - [ ] Unit 16 | - [ ] Integration 17 | - [ ] End-to-end 18 | 19 | #### How to Test 20 | 21 | Give instructions on how reviewers can verify your work 22 | 23 | #### Any background context you want to add 24 | 25 | [Delivers] 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Flutter Generated 2 | 3 | # Miscellaneous 4 | *.class 5 | *.lock 6 | *.log 7 | *.pyc 8 | *.swp 9 | .DS_Store 10 | .atom/ 11 | .buildlog/ 12 | .history 13 | .svn/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # Visual Studio Code related 22 | .vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins* 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | build/ 33 | 34 | # Android related 35 | **/android/**/gradle-wrapper.jar 36 | **/android/.gradle 37 | **/android/captures/ 38 | **/android/gradlew 39 | **/android/gradlew.bat 40 | **/android/local.properties 41 | **/android/**/GeneratedPluginRegistrant.java 42 | 43 | # iOS/XCode related 44 | **/ios/**/*.mode1v3 45 | **/ios/**/*.mode2v3 46 | **/ios/**/*.moved-aside 47 | **/ios/**/*.pbxuser 48 | **/ios/**/*.perspectivev3 49 | **/ios/**/*sync/ 50 | **/ios/**/.sconsign.dblite 51 | **/ios/**/.tags* 52 | **/ios/**/.vagrant/ 53 | **/ios/**/DerivedData/ 54 | **/ios/**/Icon? 55 | **/ios/**/Pods/ 56 | **/ios/**/.symlinks/ 57 | **/ios/**/profile 58 | **/ios/**/xcuserdata 59 | **/ios/.generated/ 60 | **/ios/Flutter/App.framework 61 | **/ios/Flutter/Flutter.framework 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/ServiceDefinitions.json 67 | **/ios/Runner/GeneratedPluginRegistrant.* 68 | 69 | 70 | ### https://raw.github.com/github/gitignore//Android.gitignore 71 | 72 | # Built application files 73 | *.apk 74 | *.ap_ 75 | 76 | # Files for the ART/Dalvik VM 77 | *.dex 78 | 79 | # Java class files 80 | *.class 81 | 82 | # Generated files 83 | bin/ 84 | gen/ 85 | out/ 86 | 87 | # Gradle files 88 | .gradle/ 89 | build/ 90 | 91 | # Local configuration file (sdk path, etc) 92 | local.properties 93 | 94 | # Proguard folder generated by Eclipse 95 | proguard/ 96 | 97 | # Log Files 98 | *.log 99 | 100 | # Android Studio Navigation editor temp files 101 | .navigation/ 102 | 103 | # Android Studio captures folder 104 | captures/ 105 | 106 | # IntelliJ 107 | *.iml 108 | .idea/workspace.xml 109 | .idea/tasks.xml 110 | .idea/gradle.xml 111 | .idea/assetWizardSettings.xml 112 | .idea/dictionaries 113 | .idea/libraries 114 | .idea/caches 115 | 116 | # Keystore files 117 | # Uncomment the following line if you do not want to check your keystore files in. 118 | #*.jks 119 | 120 | # External native build folder generated in Android Studio 2.2 and later 121 | .externalNativeBuild 122 | 123 | # Google Services (e.g. APIs or Firebase) 124 | *google-services.json 125 | *GoogleService-Info.plist 126 | 127 | # Freeline 128 | freeline.py 129 | freeline/ 130 | freeline_project_description.json 131 | 132 | # fastlane 133 | fastlane/report.xml 134 | fastlane/Preview.html 135 | fastlane/screenshots 136 | fastlane/test_output 137 | fastlane/readme.md 138 | 139 | 140 | ### https://raw.github.com/github/gitignore//Dart.gitignore 141 | 142 | # See https://www.dartlang.org/guides/libraries/private-files 143 | 144 | # Files and directories created by pub 145 | .dart_tool/ 146 | .packages 147 | build/ 148 | # If you're building an application, you may want to check-in your pubspec.lock 149 | pubspec.lock 150 | 151 | # Directory created by dartdoc 152 | # If you don't generate documentation locally you can remove this line. 153 | doc/api/ 154 | 155 | # Avoid committing generated Javascript files: 156 | *.dart.js 157 | *.info.json # Produced by the --dump-info flag. 158 | *.js # When generated by dart2js. Don't specify *.js if your 159 | # project includes source files written in JavaScript. 160 | *.js_ 161 | *.js.deps 162 | *.js.map 163 | 164 | 165 | ### https://raw.github.com/github/gitignore//Global/JetBrains.gitignore 166 | 167 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 168 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 169 | 170 | # User-specific stuff 171 | .idea/**/workspace.xml 172 | .idea/**/tasks.xml 173 | .idea/**/usage.statistics.xml 174 | .idea/**/dictionaries 175 | .idea/**/shelf 176 | 177 | # Generated files 178 | .idea/**/contentModel.xml 179 | 180 | # Sensitive or high-churn files 181 | .idea/**/dataSources/ 182 | .idea/**/dataSources.ids 183 | .idea/**/dataSources.local.xml 184 | .idea/**/sqlDataSources.xml 185 | .idea/**/dynamic.xml 186 | .idea/**/uiDesigner.xml 187 | .idea/**/dbnavigator.xml 188 | 189 | # Gradle 190 | .idea/**/gradle.xml 191 | .idea/**/libraries 192 | 193 | # Gradle and Maven with auto-import 194 | # When using Gradle or Maven with auto-import, you should exclude module files, 195 | # since they will be recreated, and may cause churn. Uncomment if using 196 | # auto-import. 197 | # .idea/modules.xml 198 | # .idea/*.iml 199 | # .idea/modules 200 | 201 | # CMake 202 | cmake-build-*/ 203 | 204 | # Mongo Explorer plugin 205 | .idea/**/mongoSettings.xml 206 | 207 | # File-based project format 208 | *.iws 209 | 210 | # IntelliJ 211 | out/ 212 | 213 | # mpeltonen/sbt-idea plugin 214 | .idea_modules/ 215 | 216 | # JIRA plugin 217 | atlassian-ide-plugin.xml 218 | 219 | # Cursive Clojure plugin 220 | .idea/replstate.xml 221 | 222 | # Crashlytics plugin (for Android Studio and IntelliJ) 223 | com_crashlytics_export_strings.xml 224 | crashlytics.properties 225 | crashlytics-build.properties 226 | fabric.properties 227 | 228 | # Editor-based Rest Client 229 | .idea/httpRequests 230 | 231 | # Android studio 3.1+ serialized cache file 232 | .idea/caches/build_file_checksums.ser 233 | 234 | 235 | ### https://raw.github.com/github/gitignore//Global/macOS.gitignore 236 | 237 | # General 238 | .DS_Store 239 | .AppleDouble 240 | .LSOverride 241 | 242 | # Icon must end with two \r 243 | Icon 244 | 245 | 246 | # Thumbnails 247 | ._* 248 | 249 | # Files that might appear in the root of a volume 250 | .DocumentRevisions-V100 251 | .fseventsd 252 | .Spotlight-V100 253 | .TemporaryItems 254 | .Trashes 255 | .VolumeIcon.icns 256 | .com.apple.timemachine.donotpresent 257 | 258 | # Directories potentially created on remote AFP share 259 | .AppleDB 260 | .AppleDesktop 261 | Network Trash Folder 262 | Temporary Items 263 | .apdisk 264 | 265 | 266 | ### https://raw.github.com/github/gitignore//Global/Xcode.gitignore 267 | 268 | # Xcode 269 | # 270 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 271 | 272 | ## User settings 273 | xcuserdata/ 274 | 275 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 276 | *.xcscmblueprint 277 | *.xccheckout 278 | 279 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 280 | build/ 281 | DerivedData/ 282 | *.moved-aside 283 | *.pbxuser 284 | !default.pbxuser 285 | *.mode1v3 286 | !default.mode1v3 287 | *.mode2v3 288 | !default.mode2v3 289 | *.perspectivev3 290 | !default.perspectivev3 291 | 292 | 293 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Archives.gitignore 294 | 295 | # It's better to unpack these files and commit the raw source because 296 | # git has its own built in compression methods. 297 | *.7z 298 | *.jar 299 | *.rar 300 | *.zip 301 | *.gz 302 | *.tgz 303 | *.bzip 304 | *.bz2 305 | *.xz 306 | *.lzma 307 | *.cab 308 | 309 | # Packing-only formats 310 | *.iso 311 | *.tar 312 | 313 | # Package management formats 314 | *.dmg 315 | *.xpi 316 | *.gem 317 | *.egg 318 | *.deb 319 | *.rpm 320 | *.msi 321 | *.msm 322 | *.msp 323 | 324 | 325 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Backup.gitignore 326 | 327 | *.bak 328 | *.gho 329 | *.ori 330 | *.orig 331 | *.tmp 332 | 333 | 334 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Android.gitignore 335 | 336 | # Built application files 337 | *.apk 338 | *.ap_ 339 | 340 | # Files for the ART/Dalvik VM 341 | *.dex 342 | 343 | # Java class files 344 | *.class 345 | 346 | # Generated files 347 | bin/ 348 | gen/ 349 | out/ 350 | 351 | # Gradle files 352 | .gradle/ 353 | build/ 354 | 355 | # Local configuration file (sdk path, etc) 356 | local.properties 357 | 358 | # Proguard folder generated by Eclipse 359 | proguard/ 360 | 361 | # Log Files 362 | *.log 363 | 364 | # Android Studio Navigation editor temp files 365 | .navigation/ 366 | 367 | # Android Studio captures folder 368 | captures/ 369 | 370 | # IntelliJ 371 | *.iml 372 | .idea/workspace.xml 373 | .idea/tasks.xml 374 | .idea/gradle.xml 375 | .idea/assetWizardSettings.xml 376 | .idea/dictionaries 377 | .idea/libraries 378 | .idea/caches 379 | 380 | # Keystore files 381 | # Uncomment the following line if you do not want to check your keystore files in. 382 | #*.jks 383 | 384 | # External native build folder generated in Android Studio 2.2 and later 385 | .externalNativeBuild 386 | 387 | # Google Services (e.g. APIs or Firebase) 388 | google-services.json 389 | 390 | # Freeline 391 | freeline.py 392 | freeline/ 393 | freeline_project_description.json 394 | 395 | # fastlane 396 | fastlane/report.xml 397 | fastlane/Preview.html 398 | fastlane/screenshots 399 | fastlane/test_output 400 | fastlane/readme.md 401 | 402 | 403 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Dart.gitignore 404 | 405 | # See https://www.dartlang.org/guides/libraries/private-files 406 | 407 | # Files and directories created by pub 408 | .dart_tool/ 409 | .packages 410 | build/ 411 | # If you're building an application, you may want to check-in your pubspec.lock 412 | pubspec.lock 413 | 414 | # Directory created by dartdoc 415 | # If you don't generate documentation locally you can remove this line. 416 | doc/api/ 417 | 418 | # Avoid committing generated Javascript files: 419 | *.dart.js 420 | *.info.json # Produced by the --dump-info flag. 421 | *.js # When generated by dart2js. Don't specify *.js if your 422 | # project includes source files written in JavaScript. 423 | *.js_ 424 | *.js.deps 425 | *.js.map 426 | 427 | 428 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/DartEditor.gitignore 429 | 430 | .project 431 | .buildlog 432 | 433 | 434 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Emacs.gitignore 435 | 436 | # -*- mode: gitignore; -*- 437 | *~ 438 | \#*\# 439 | /.emacs.desktop 440 | /.emacs.desktop.lock 441 | *.elc 442 | auto-save-list 443 | tramp 444 | .\#* 445 | 446 | # Org-mode 447 | .org-id-locations 448 | *_archive 449 | 450 | # flymake-mode 451 | *_flymake.* 452 | 453 | # eshell files 454 | /eshell/history 455 | /eshell/lastdir 456 | 457 | # elpa packages 458 | /elpa/ 459 | 460 | # reftex files 461 | *.rel 462 | 463 | # AUCTeX auto folder 464 | /auto/ 465 | 466 | # cask packages 467 | .cask/ 468 | dist/ 469 | 470 | # Flycheck 471 | flycheck_*.el 472 | 473 | # server auth directory 474 | /server/ 475 | 476 | # projectiles files 477 | .projectile 478 | 479 | # directory configuration 480 | .dir-locals.el 481 | 482 | 483 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Gradle.gitignore 484 | 485 | .gradle 486 | /build/ 487 | 488 | # Ignore Gradle GUI config 489 | gradle-app.setting 490 | 491 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 492 | !gradle-wrapper.jar 493 | 494 | # Cache of project 495 | .gradletasknamecache 496 | 497 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 498 | # gradle/wrapper/gradle-wrapper.properties 499 | 500 | 501 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Java.gitignore 502 | 503 | # Compiled class file 504 | *.class 505 | 506 | # Log file 507 | *.log 508 | 509 | # BlueJ files 510 | *.ctxt 511 | 512 | # Mobile Tools for Java (J2ME) 513 | .mtj.tmp/ 514 | 515 | # Package Files # 516 | *.jar 517 | *.war 518 | *.nar 519 | *.ear 520 | *.zip 521 | *.tar.gz 522 | *.rar 523 | 524 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 525 | hs_err_pid* 526 | 527 | 528 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/JetBrains.gitignore 529 | 530 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 531 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 532 | 533 | # User-specific stuff 534 | .idea/**/workspace.xml 535 | .idea/**/tasks.xml 536 | .idea/**/usage.statistics.xml 537 | .idea/**/dictionaries 538 | .idea/**/shelf 539 | 540 | # Generated files 541 | .idea/**/contentModel.xml 542 | 543 | # Sensitive or high-churn files 544 | .idea/**/dataSources/ 545 | .idea/**/dataSources.ids 546 | .idea/**/dataSources.local.xml 547 | .idea/**/sqlDataSources.xml 548 | .idea/**/dynamic.xml 549 | .idea/**/uiDesigner.xml 550 | .idea/**/dbnavigator.xml 551 | 552 | # Gradle 553 | .idea/**/gradle.xml 554 | .idea/**/libraries 555 | 556 | # Gradle and Maven with auto-import 557 | # When using Gradle or Maven with auto-import, you should exclude module files, 558 | # since they will be recreated, and may cause churn. Uncomment if using 559 | # auto-import. 560 | # .idea/modules.xml 561 | # .idea/*.iml 562 | # .idea/modules 563 | 564 | # CMake 565 | cmake-build-*/ 566 | 567 | # Mongo Explorer plugin 568 | .idea/**/mongoSettings.xml 569 | 570 | # File-based project format 571 | *.iws 572 | 573 | # IntelliJ 574 | out/ 575 | 576 | # mpeltonen/sbt-idea plugin 577 | .idea_modules/ 578 | 579 | # JIRA plugin 580 | atlassian-ide-plugin.xml 581 | 582 | # Cursive Clojure plugin 583 | .idea/replstate.xml 584 | 585 | # Crashlytics plugin (for Android Studio and IntelliJ) 586 | com_crashlytics_export_strings.xml 587 | crashlytics.properties 588 | crashlytics-build.properties 589 | fabric.properties 590 | 591 | # Editor-based Rest Client 592 | .idea/httpRequests 593 | 594 | # Android studio 3.1+ serialized cache file 595 | .idea/caches/build_file_checksums.ser 596 | 597 | 598 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/JEnv.gitignore 599 | 600 | # JEnv local Java version configuration file 601 | .java-version 602 | 603 | # Used by previous versions of JEnv 604 | .jenv-version 605 | 606 | 607 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Kotlin.gitignore 608 | 609 | # Compiled class file 610 | *.class 611 | 612 | # Log file 613 | *.log 614 | 615 | # BlueJ files 616 | *.ctxt 617 | 618 | # Mobile Tools for Java (J2ME) 619 | .mtj.tmp/ 620 | 621 | # Package Files # 622 | *.jar 623 | *.war 624 | *.nar 625 | *.ear 626 | *.zip 627 | *.tar.gz 628 | *.rar 629 | 630 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 631 | hs_err_pid* 632 | 633 | 634 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Linux.gitignore 635 | 636 | *~ 637 | 638 | # temporary files which can be created if a process still has a handle open of a deleted file 639 | .fuse_hidden* 640 | 641 | # KDE directory preferences 642 | .directory 643 | 644 | # Linux trash folder which might appear on any partition or disk 645 | .Trash-* 646 | 647 | # .nfs files are created when an open file is removed but is still being accessed 648 | .nfs* 649 | 650 | 651 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/macOS.gitignore 652 | 653 | # General 654 | .DS_Store 655 | .AppleDouble 656 | .LSOverride 657 | 658 | # Icon must end with two \r 659 | Icon 660 | 661 | 662 | # Thumbnails 663 | ._* 664 | 665 | # Files that might appear in the root of a volume 666 | .DocumentRevisions-V100 667 | .fseventsd 668 | .Spotlight-V100 669 | .TemporaryItems 670 | .Trashes 671 | .VolumeIcon.icns 672 | .com.apple.timemachine.donotpresent 673 | 674 | # Directories potentially created on remote AFP share 675 | .AppleDB 676 | .AppleDesktop 677 | Network Trash Folder 678 | Temporary Items 679 | .apdisk 680 | 681 | 682 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Maven.gitignore 683 | 684 | target/ 685 | pom.xml.tag 686 | pom.xml.releaseBackup 687 | pom.xml.versionsBackup 688 | pom.xml.next 689 | release.properties 690 | dependency-reduced-pom.xml 691 | buildNumber.properties 692 | .mvn/timing.properties 693 | .mvn/wrapper/maven-wrapper.jar 694 | 695 | 696 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Objective-C.gitignore 697 | 698 | # Xcode 699 | # 700 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 701 | 702 | ## Build generated 703 | build/ 704 | DerivedData/ 705 | 706 | ## Various settings 707 | *.pbxuser 708 | !default.pbxuser 709 | *.mode1v3 710 | !default.mode1v3 711 | *.mode2v3 712 | !default.mode2v3 713 | *.perspectivev3 714 | !default.perspectivev3 715 | xcuserdata/ 716 | 717 | ## Other 718 | *.moved-aside 719 | *.xccheckout 720 | *.xcscmblueprint 721 | 722 | ## Obj-C/Swift specific 723 | *.hmap 724 | *.ipa 725 | *.dSYM.zip 726 | *.dSYM 727 | 728 | # CocoaPods 729 | # 730 | # We recommend against adding the Pods directory to your .gitignore. However 731 | # you should judge for yourself, the pros and cons are mentioned at: 732 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 733 | # 734 | # Pods/ 735 | # 736 | # Add this line if you want to avoid checking in source code from the Xcode workspace 737 | # *.xcworkspace 738 | 739 | # Carthage 740 | # 741 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 742 | # Carthage/Checkouts 743 | 744 | Carthage/Build 745 | 746 | # fastlane 747 | # 748 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 749 | # screenshots whenever they are needed. 750 | # For more information about the recommended setup visit: 751 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 752 | 753 | fastlane/report.xml 754 | fastlane/Preview.html 755 | fastlane/screenshots/**/*.png 756 | fastlane/test_output 757 | 758 | # Code Injection 759 | # 760 | # After new code Injection tools there's a generated folder /iOSInjectionProject 761 | # https://github.com/johnno1962/injectionforxcode 762 | 763 | iOSInjectionProject/ 764 | 765 | 766 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/SublimeText.gitignore 767 | 768 | # Cache files for Sublime Text 769 | *.tmlanguage.cache 770 | *.tmPreferences.cache 771 | *.stTheme.cache 772 | 773 | # Workspace files are user-specific 774 | *.sublime-workspace 775 | 776 | # Project files should be checked into the repository, unless a significant 777 | # proportion of contributors will probably not be using Sublime Text 778 | # *.sublime-project 779 | 780 | # SFTP configuration file 781 | sftp-config.json 782 | 783 | # Package control specific files 784 | Package Control.last-run 785 | Package Control.ca-list 786 | Package Control.ca-bundle 787 | Package Control.system-ca-bundle 788 | Package Control.cache/ 789 | Package Control.ca-certs/ 790 | Package Control.merged-ca-bundle 791 | Package Control.user-ca-bundle 792 | oscrypto-ca-bundle.crt 793 | bh_unicode_properties.cache 794 | 795 | # Sublime-github package stores a github token in this file 796 | # https://packagecontrol.io/packages/sublime-github 797 | GitHub.sublime-settings 798 | 799 | 800 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Vim.gitignore 801 | 802 | # Swap 803 | [._]*.s[a-v][a-z] 804 | [._]*.sw[a-p] 805 | [._]s[a-rt-v][a-z] 806 | [._]ss[a-gi-z] 807 | [._]sw[a-p] 808 | 809 | # Session 810 | Session.vim 811 | 812 | # Temporary 813 | .netrwhist 814 | *~ 815 | # Auto-generated tag files 816 | tags 817 | # Persistent undo 818 | [._]*.un~ 819 | 820 | 821 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/VisualStudioCode.gitignore 822 | 823 | .vscode/* 824 | !.vscode/settings.json 825 | !.vscode/tasks.json 826 | !.vscode/launch.json 827 | !.vscode/extensions.json 828 | 829 | 830 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Windows.gitignore 831 | 832 | # Windows thumbnail cache files 833 | Thumbs.db 834 | ehthumbs.db 835 | ehthumbs_vista.db 836 | 837 | # Dump file 838 | *.stackdump 839 | 840 | # Folder config file 841 | [Dd]esktop.ini 842 | 843 | # Recycle Bin used on file shares 844 | $RECYCLE.BIN/ 845 | 846 | # Windows Installer files 847 | *.cab 848 | *.msi 849 | *.msix 850 | *.msm 851 | *.msp 852 | 853 | # Windows shortcuts 854 | *.lnk 855 | 856 | 857 | ### https://raw.github.com/github/gitignore/340e2fe08a2356c2e3760ff58c3a9e1fddf08060/Global/Xcode.gitignore 858 | 859 | # Xcode 860 | # 861 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 862 | 863 | ## User settings 864 | xcuserdata/ 865 | 866 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 867 | *.xcscmblueprint 868 | *.xccheckout 869 | 870 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 871 | build/ 872 | DerivedData/ 873 | *.moved-aside 874 | *.pbxuser 875 | !default.pbxuser 876 | *.mode1v3 877 | !default.mode1v3 878 | *.mode2v3 879 | !default.mode2v3 880 | *.perspectivev3 881 | !default.perspectivev3 882 | 883 | 884 | ### Flutter Generated Exceptions 885 | 886 | # Exceptions to above rules. 887 | !**/ios/**/default.mode1v3 888 | !**/ios/**/default.mode2v3 889 | !**/ios/**/default.pbxuser 890 | !**/ios/**/default.perspectivev3 891 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 892 | TODO.md -------------------------------------------------------------------------------- /.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: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Task manager app 2 | A fancy task manager application for learning Flutter, Bloc pattern and Firebase 3 | 4 | ## Technologies used 5 | - Flutter 6 | - Bloc 7 | - Dart 8 | - Firebase 9 | 10 | ## How to use it? 11 | You can follow these instructions to build the task manager application and install it onto your device. 12 | 13 | ### Prerequisites 14 | If you are new to Flutter, please first follow 15 | the [Flutter Setup](https://flutter.dev/setup/) guide. 16 | 17 | ### Building and installing the task manager app 18 | * `git clone https://github.com/coolbeatz71/task_manager_flutter/` 19 | * `cd task_manager_flutter` 20 | * `flutter pub get` 21 | * `flutter run --release` 22 | 23 | The `flutter run --release` command both builds and installs the Flutter app. 24 | 25 | ## App user interface showcase 26 | I tried my best to make the User interface more fancy and attractive, here are some illustration pages 27 | 28 | ![image](https://user-images.githubusercontent.com/25999336/76682601-254f1c80-6606-11ea-9e9e-7a4627427c60.png) 29 | ![image](https://user-images.githubusercontent.com/25999336/76682603-27b17680-6606-11ea-999f-9c7ca5338cb7.png) 30 | 31 | ![image](https://user-images.githubusercontent.com/25999336/76682605-2a13d080-6606-11ea-9241-96088aa7307b.png) 32 | ![image](https://user-images.githubusercontent.com/25999336/76682607-2c762a80-6606-11ea-9113-30b96c7ada8f.png) 33 | 34 | ![image](https://user-images.githubusercontent.com/25999336/76682609-2f711b00-6606-11ea-90d6-ffcf064284fb.png) 35 | ![image](https://user-images.githubusercontent.com/25999336/76682611-326c0b80-6606-11ea-99bd-0742bb2f7313.png) 36 | 37 | ![image](https://user-images.githubusercontent.com/25999336/76682613-35ff9280-6606-11ea-8d0a-fb39395dfc4c.png) 38 | ![image](https://user-images.githubusercontent.com/25999336/76682615-3861ec80-6606-11ea-8d64-4bcd0fd7eecb.png) 39 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.coolbeatz71.flutter_todo_app" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | multiDexEnabled true 48 | } 49 | 50 | buildTypes { 51 | release { 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.debug 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source '../..' 61 | } 62 | 63 | dependencies { 64 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 65 | testImplementation 'junit:junit:4.12' 66 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 67 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 68 | implementation 'com.google.firebase:firebase-analytics:17.2.2' 69 | implementation 'androidx.multidex:multidex:2.0.1' 70 | } 71 | 72 | apply plugin: 'com.google.gms.google-services' 73 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 15 | 22 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/coolbeatz71/todo_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.coolbeatz71.todo_app 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/todo_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.todo_app 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/android/app/src/main/res/drawable/app_icon.png -------------------------------------------------------------------------------- /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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/raw/keep.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /android/app/src/main/res/raw/wecker_sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/android/app/src/main/res/raw/wecker_sound.mp3 -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.0' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.3.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.3' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.enableR8=true 4 | 5 | android.useAndroidX=true 6 | android.enableJetifier=true 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/images/dont-know.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/assets/images/dont-know.gif -------------------------------------------------------------------------------- /assets/images/to-do-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/images/undraw_no_data.svg: -------------------------------------------------------------------------------- 1 | no data -------------------------------------------------------------------------------- /fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/fonts/OpenSans-ExtraBold.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/fonts/OpenSans-SemiBold.ttf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/mutombojean-vincent/desktop/tools/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/mutombojean-vincent/Desktop/projects/todo_app" 5 | export "FLUTTER_TARGET=/Users/mutombojean-vincent/Desktop/projects/todo_app/lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "FLUTTER_FRAMEWORK_DIR=/Users/mutombojean-vincent/desktop/tools/flutter/bin/cache/artifacts/engine/ios" 9 | export "FLUTTER_BUILD_NAME=1.0.0" 10 | export "FLUTTER_BUILD_NUMBER=1" 11 | export "TRACK_WIDGET_CREATION=true" 12 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | pod 'Firebase/Analytics' 40 | 41 | # Flutter Pod 42 | 43 | copied_flutter_dir = File.join(__dir__, 'Flutter') 44 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 45 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 46 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 47 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 48 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 49 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 50 | 51 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 52 | unless File.exist?(generated_xcode_build_settings_path) 53 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 54 | end 55 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 56 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 57 | 58 | unless File.exist?(copied_framework_path) 59 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 60 | end 61 | unless File.exist?(copied_podspec_path) 62 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 63 | end 64 | end 65 | 66 | # Keep pod path relative so it can be checked into Podfile.lock. 67 | pod 'Flutter', :path => 'Flutter' 68 | 69 | # Plugin Pods 70 | 71 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 72 | # referring to absolute paths on developers' machines. 73 | system('rm -rf .symlinks') 74 | system('mkdir -p .symlinks/plugins') 75 | plugin_pods = parse_KV_file('../.flutter-plugins') 76 | plugin_pods.each do |name, path| 77 | symlink = File.join('.symlinks', 'plugins', name) 78 | File.symlink(path, symlink) 79 | pod name, :path => File.join(symlink, 'ios') 80 | end 81 | end 82 | 83 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 84 | install! 'cocoapods', :disable_input_output_paths => true 85 | 86 | post_install do |installer| 87 | installer.pods_project.targets.each do |target| 88 | target.build_configurations.each do |config| 89 | config.build_settings['ENABLE_BITCODE'] = 'NO' 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /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/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import Firebase 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | FirebaseApp.configure() 12 | GeneratedPluginRegistrant.register(with: self) 13 | if #available(iOS 10.0, *) { 14 | UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate 15 | } 16 | if(!UserDefaults.standard.bool(forKey: "Notification")) { 17 | UIApplication.shared.cancelAllLocalNotifications() 18 | UserDefaults.standard.set(true, forKey: "Notification") 19 | } 20 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/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 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | todo_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /ios/wecker_sound.aiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coolbeatz71/task_manager_flutter/7a0f5d0cc06a1e171d60f0fd824730b4eb9481a9/ios/wecker_sound.aiff -------------------------------------------------------------------------------- /lib/bloc/task_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:cloud_firestore/cloud_firestore.dart'; 5 | import 'package:equatable/equatable.dart'; 6 | import 'package:todo_app/models/task.dart'; 7 | import 'package:todo_app/services/firestore.dart'; 8 | import 'package:todo_app/services/notification.dart'; 9 | 10 | part 'task_event.dart'; 11 | part 'task_state.dart'; 12 | 13 | class TaskBloc extends Bloc { 14 | final FirestoreService _firestore = FirestoreService(); 15 | 16 | @override 17 | TaskState get initialState => TaskInitial(); 18 | 19 | @override 20 | Stream mapEventToState(TaskEvent event) async* { 21 | if (event is CreateTaskEvent) { 22 | yield* _mapCreateTask(event); 23 | } else if (event is DeleteTaskEvent) { 24 | yield* _mapDeleteTask(event); 25 | } else if (event is CompleteTaskEvent) { 26 | yield* _mapCompleteTask(event); 27 | } else if (event is UpdateTaskEvent) { 28 | yield* _mapUpdateTask(event); 29 | } else if (event is TaskReminderEvent) { 30 | yield* _mapTaskReminder(event); 31 | } 32 | } 33 | 34 | Stream _mapCreateTask(CreateTaskEvent event) async* { 35 | yield TaskLoading(); 36 | DocumentReference ref = await _firestore.createTask(event.task); 37 | 38 | // schedule reminder 39 | if (event.task.isReminderSet == true) 40 | await Reminder().setup( 41 | ref.documentID, 42 | event.task, 43 | ); 44 | yield TaskSubmitted(); 45 | } 46 | 47 | Stream _mapDeleteTask(DeleteTaskEvent event) async* { 48 | yield TaskDeleting(); 49 | await _firestore.deleteTask(event.taskId); 50 | } 51 | 52 | Stream _mapCompleteTask(CompleteTaskEvent event) async* { 53 | yield TaskCompleting(); 54 | await _firestore.completeTask(event.taskId, event.isCompleted); 55 | yield TaskCompleted(); 56 | } 57 | 58 | Stream _mapTaskReminder(TaskReminderEvent event) async* { 59 | yield TaskReminderSetting(); 60 | await _firestore.setTaskReminder(event.task.id, event.isReminderSet); 61 | 62 | // schedule reminder 63 | if (event.isReminderSet == true) 64 | await Reminder().setup( 65 | event.task.id, 66 | event.task, 67 | ); 68 | yield TaskReminderSet(); 69 | } 70 | 71 | Stream _mapUpdateTask(UpdateTaskEvent event) async* { 72 | yield TaskLoading(); 73 | await _firestore.updateTask(event.taskId, event.task); 74 | 75 | // schedule reminder 76 | if (event.task.isReminderSet == true) 77 | await Reminder().setup( 78 | event.taskId, 79 | event.task, 80 | ); 81 | yield TaskSubmitted(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/bloc/task_event.dart: -------------------------------------------------------------------------------- 1 | part of 'task_bloc.dart'; 2 | 3 | abstract class TaskEvent extends Equatable { 4 | const TaskEvent(); 5 | } 6 | 7 | class CreateTaskEvent extends TaskEvent { 8 | final Task task; 9 | 10 | CreateTaskEvent(this.task); 11 | 12 | @override 13 | List get props => [task]; 14 | } 15 | 16 | class DeleteTaskEvent extends TaskEvent { 17 | final String taskId; 18 | 19 | DeleteTaskEvent(this.taskId); 20 | 21 | @override 22 | List get props => [taskId]; 23 | } 24 | 25 | class CompleteTaskEvent extends TaskEvent { 26 | final String taskId; 27 | final bool isCompleted; 28 | 29 | CompleteTaskEvent(this.taskId, this.isCompleted); 30 | 31 | @override 32 | List get props => [taskId, isCompleted]; 33 | } 34 | 35 | class TaskReminderEvent extends TaskEvent { 36 | final Task task; 37 | final bool isReminderSet; 38 | 39 | TaskReminderEvent(this.task, this.isReminderSet); 40 | 41 | @override 42 | List get props => [task, isReminderSet]; 43 | } 44 | 45 | class UpdateTaskEvent extends TaskEvent { 46 | final String taskId; 47 | final Task task; 48 | 49 | UpdateTaskEvent(this.taskId, this.task); 50 | 51 | @override 52 | List get props => [taskId, task]; 53 | } 54 | -------------------------------------------------------------------------------- /lib/bloc/task_state.dart: -------------------------------------------------------------------------------- 1 | part of 'task_bloc.dart'; 2 | 3 | abstract class TaskState extends Equatable { 4 | const TaskState(); 5 | } 6 | 7 | class TaskInitial extends TaskState { 8 | @override 9 | List get props => []; 10 | } 11 | 12 | class TaskLoading extends TaskState { 13 | @override 14 | List get props => []; 15 | } 16 | 17 | class TaskCompleting extends TaskState { 18 | @override 19 | List get props => []; 20 | } 21 | 22 | class TaskReminderSetting extends TaskState { 23 | @override 24 | List get props => []; 25 | } 26 | 27 | class TaskDeleting extends TaskState { 28 | @override 29 | List get props => []; 30 | } 31 | 32 | class TaskSubmitted extends TaskState { 33 | @override 34 | List get props => []; 35 | } 36 | 37 | class TaskLoaded extends TaskState { 38 | final List task; 39 | const TaskLoaded([this.task = const []]); 40 | 41 | @override 42 | List get props => [task]; 43 | } 44 | 45 | class TaskNotLoaded extends TaskState { 46 | @override 47 | List get props => []; 48 | } 49 | 50 | class TaskCompleted extends TaskState { 51 | @override 52 | List get props => []; 53 | } 54 | 55 | class TaskReminderSet extends TaskState { 56 | @override 57 | List get props => []; 58 | } 59 | -------------------------------------------------------------------------------- /lib/core/custom_switch.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSwitch extends StatefulWidget { 4 | final bool value; 5 | final ValueChanged onChanged; 6 | final Color activeColor; 7 | final Color inactiveColor; 8 | final String activeText; 9 | final String inactiveText; 10 | final Color activeTextColor; 11 | final Color inactiveTextColor; 12 | 13 | const CustomSwitch({ 14 | Key key, 15 | this.value, 16 | this.onChanged, 17 | this.activeColor, 18 | this.activeText = 'On', 19 | this.inactiveText = 'Off', 20 | this.inactiveColor = Colors.black26, 21 | this.activeTextColor = Colors.white70, 22 | this.inactiveTextColor = Colors.white70, 23 | }) : super(key: key); 24 | 25 | @override 26 | _CustomSwitchState createState() => _CustomSwitchState(); 27 | } 28 | 29 | class _CustomSwitchState extends State 30 | with SingleTickerProviderStateMixin { 31 | Animation _circleAnimation; 32 | AnimationController _animationController; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | _animationController = 38 | AnimationController(vsync: this, duration: Duration(milliseconds: 60)); 39 | _circleAnimation = AlignmentTween( 40 | begin: widget.value ? Alignment.centerRight : Alignment.centerLeft, 41 | end: widget.value ? Alignment.centerLeft : Alignment.centerRight) 42 | .animate( 43 | CurvedAnimation( 44 | parent: _animationController, 45 | curve: Curves.linear, 46 | ), 47 | ); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return AnimatedBuilder( 53 | animation: _animationController, 54 | builder: (context, child) { 55 | return GestureDetector( 56 | onTap: () { 57 | if (_animationController.isCompleted) { 58 | _animationController.reverse(); 59 | } else { 60 | _animationController.forward(); 61 | } 62 | widget.value == false 63 | ? widget.onChanged(true) 64 | : widget.onChanged(false); 65 | }, 66 | child: Container( 67 | width: 49.0, 68 | height: 22.0, 69 | decoration: BoxDecoration( 70 | borderRadius: BorderRadius.circular(20.0), 71 | color: _circleAnimation.value == Alignment.centerLeft 72 | ? widget.inactiveColor 73 | : widget.activeColor, 74 | ), 75 | child: Padding( 76 | padding: const EdgeInsets.all(2.0), 77 | child: Row( 78 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 79 | children: [ 80 | _circleAnimation.value == Alignment.centerRight 81 | ? Padding( 82 | padding: const EdgeInsets.only(left: 4.0, right: 4.0), 83 | child: Text( 84 | widget.activeText, 85 | style: TextStyle( 86 | color: widget.activeTextColor, 87 | fontWeight: FontWeight.w900, 88 | fontSize: 13.0, 89 | ), 90 | ), 91 | ) 92 | : Container(), 93 | Align( 94 | alignment: _circleAnimation.value, 95 | child: Container( 96 | width: 18.0, 97 | height: 18.0, 98 | decoration: BoxDecoration( 99 | shape: BoxShape.circle, 100 | color: Colors.white, 101 | ), 102 | ), 103 | ), 104 | _circleAnimation.value == Alignment.centerLeft 105 | ? Padding( 106 | padding: const EdgeInsets.only(left: 4.0, right: 5.0), 107 | child: Text( 108 | widget.inactiveText, 109 | style: TextStyle( 110 | color: widget.inactiveTextColor, 111 | fontWeight: FontWeight.w400, 112 | fontSize: 12.0, 113 | ), 114 | ), 115 | ) 116 | : Container(), 117 | ], 118 | ), 119 | ), 120 | ), 121 | ); 122 | }, 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/core/dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | 4 | class AppDialog extends StatelessWidget { 5 | final String title; 6 | final String description; 7 | final Function onPressed; 8 | 9 | const AppDialog({ 10 | Key key, 11 | @required this.title, 12 | @required this.description, 13 | this.onPressed, 14 | }) : super(key: key); 15 | @override 16 | Widget build(BuildContext context) { 17 | return Dialog( 18 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 19 | elevation: 0, 20 | backgroundColor: Colors.transparent, 21 | child: _buildChild(context), 22 | ); 23 | } 24 | 25 | _buildChild(BuildContext context) => Container( 26 | height: 320, 27 | decoration: BoxDecoration( 28 | color: Colors.white, 29 | shape: BoxShape.rectangle, 30 | borderRadius: BorderRadius.all( 31 | Radius.circular(12), 32 | ), 33 | ), 34 | child: Column( 35 | children: [ 36 | Container( 37 | child: Padding( 38 | padding: const EdgeInsets.all(12.0), 39 | child: Image.asset( 40 | 'assets/images/dont-know.gif', 41 | height: 120, 42 | width: 120, 43 | ), 44 | ), 45 | width: double.infinity, 46 | decoration: BoxDecoration( 47 | color: Colors.white, 48 | shape: BoxShape.rectangle, 49 | borderRadius: BorderRadius.only( 50 | topLeft: Radius.circular(12), 51 | topRight: Radius.circular(12), 52 | ), 53 | ), 54 | ), 55 | Text( 56 | title, 57 | style: TextStyle( 58 | fontSize: 22, 59 | color: AppColors.dark, 60 | fontFamily: 'Open Sans', 61 | fontWeight: FontWeight.w600, 62 | ), 63 | ), 64 | SizedBox(height: 8), 65 | Padding( 66 | padding: const EdgeInsets.only(right: 16, left: 16), 67 | child: Text( 68 | description, 69 | style: TextStyle( 70 | fontSize: 14, 71 | fontFamily: 'Open Sans', 72 | fontWeight: FontWeight.w300, 73 | ), 74 | textAlign: TextAlign.center, 75 | ), 76 | ), 77 | SizedBox(height: 50), 78 | Center( 79 | child: FlatButton( 80 | color: AppColors.primary, 81 | shape: RoundedRectangleBorder( 82 | borderRadius: BorderRadius.circular(4.0), 83 | ), 84 | padding: EdgeInsets.symmetric(vertical: 18, horizontal: 40), 85 | splashColor: AppColors.primary, 86 | onPressed: onPressed, 87 | child: Text( 88 | 'Ok', 89 | style: TextStyle( 90 | fontSize: 14, 91 | fontFamily: 'Open Sans', 92 | fontWeight: FontWeight.w300, 93 | ), 94 | ), 95 | textColor: Colors.white, 96 | ), 97 | ), 98 | ], 99 | ), 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /lib/core/flushbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flushbar/flushbar.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:todo_app/helpers/colors.dart'; 4 | 5 | class AppFlushBar { 6 | final BuildContext context; 7 | final String title; 8 | final String message; 9 | final String actionText; 10 | final double padding; 11 | final Function onPressed; 12 | 13 | AppFlushBar({ 14 | @required this.context, 15 | @required this.title, 16 | @required this.message, 17 | @required this.actionText, 18 | @required this.onPressed, 19 | this.padding = 18.0, 20 | }); 21 | 22 | Flushbar show() { 23 | return Flushbar( 24 | icon: Icon( 25 | Icons.help, 26 | color: Colors.white, 27 | size: 35, 28 | ), 29 | titleText: Text( 30 | title, 31 | style: TextStyle( 32 | color: Colors.white, 33 | fontFamily: 'Open Sans', 34 | fontWeight: FontWeight.w900, 35 | ), 36 | ), 37 | messageText: Text( 38 | message, 39 | style: TextStyle( 40 | color: Colors.white, 41 | fontSize: 13, 42 | fontFamily: 'Open Sans', 43 | fontWeight: FontWeight.w300, 44 | ), 45 | ), 46 | showProgressIndicator: true, 47 | progressIndicatorBackgroundColor: AppColors.darkGrey, 48 | progressIndicatorValueColor: AlwaysStoppedAnimation( 49 | AppColors.primary, 50 | ), 51 | padding: EdgeInsets.all(padding), 52 | shouldIconPulse: true, 53 | flushbarPosition: FlushbarPosition.BOTTOM, 54 | animationDuration: const Duration(milliseconds: 500), 55 | forwardAnimationCurve: Curves.elasticOut, 56 | reverseAnimationCurve: Curves.linear, 57 | duration: Duration(seconds: 4), 58 | mainButton: FlatButton( 59 | shape: new RoundedRectangleBorder( 60 | side: BorderSide( 61 | color: AppColors.primary, 62 | style: BorderStyle.solid, 63 | ), 64 | borderRadius: new BorderRadius.circular(10.0), 65 | ), 66 | onPressed: () { 67 | onPressed(); 68 | Navigator.of(context).pop(); 69 | }, 70 | child: Text( 71 | actionText, 72 | style: TextStyle( 73 | color: AppColors.primary, 74 | fontFamily: 'Open Sans', 75 | fontWeight: FontWeight.w300, 76 | ), 77 | ), 78 | ), 79 | )..show(context); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/core/validation.dart: -------------------------------------------------------------------------------- 1 | class Validate { 2 | static getMsg(String field, String value) { 3 | final RegExp _regex = RegExp(r"^[a-zA-Z0-9\-\.\,\;\:\'\(\)\s]+$"); 4 | 5 | if (value == null || value.isEmpty || value.trim().isEmpty) { 6 | return 'The $field cannot be empty'; 7 | } else if (!_regex.hasMatch(value)) { 8 | return 'The $field cannot contain special characters'; 9 | } else if (value.length < 10) { 10 | return 'The $field must be at least 10 characters long'; 11 | } 12 | 13 | return null; 14 | } 15 | 16 | static getDateTimeMsg(String field, String value) { 17 | if (value == null || value.isEmpty || value.trim().isEmpty) { 18 | return 'Please, select a $field'; 19 | } 20 | 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/helpers/animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:simple_animations/simple_animations.dart'; 3 | 4 | class FadeAnimation extends StatelessWidget { 5 | final double delay; 6 | final Widget child; 7 | 8 | FadeAnimation(this.delay, {this.child}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final tween = MultiTrackTween([ 13 | Track("opacity") 14 | .add(Duration(milliseconds: 300), Tween(begin: 0.0, end: 1.0)), 15 | Track("translateY").add( 16 | Duration(milliseconds: 300), Tween(begin: -30.0, end: 0.0), 17 | curve: Curves.easeOut) 18 | ]); 19 | 20 | return ControlledAnimation( 21 | delay: Duration(milliseconds: (500 * delay).round()), 22 | duration: tween.duration, 23 | tween: tween, 24 | child: child, 25 | builderWithChild: (context, child, animation) => Opacity( 26 | opacity: animation["opacity"], 27 | child: Transform.translate( 28 | offset: Offset( 29 | 0, 30 | animation["translateY"], 31 | ), 32 | child: child, 33 | ), 34 | ), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/helpers/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColors { 4 | AppColors._(); 5 | 6 | static const Color dark = Colors.black; 7 | static const Color darkGrey = Color(0x50000000); 8 | static const Color primary = Color(0xFFEC407A); 9 | static const Color primaryLight = Color(0xFFf48fb1); 10 | static const Color primarySoft = Color(0xFFF6E1EC); 11 | static const Color primaryAccent = Color(0xFFAD1457); 12 | static const Color secondary = Color(0xFF241B50); 13 | static const Color disabled = Color(0xFFEBEBE4); 14 | static const Color lightGrey = Color(0xFFf5f5f5); 15 | static const Color greenAccent = Color(0xFF4CAF50); 16 | } 17 | -------------------------------------------------------------------------------- /lib/helpers/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_flexible_toast/flutter_flexible_toast.dart'; 5 | import 'package:intl/intl.dart'; 6 | import 'package:todo_app/helpers/colors.dart'; 7 | import 'package:todo_app/models/task.dart'; 8 | import 'package:todo_app/views/pages/home/bottomsheet.dart'; 9 | 10 | enum TaskPageStatus { 11 | all, 12 | completed, 13 | active, 14 | details, 15 | } 16 | 17 | class Utils { 18 | Utils(); 19 | 20 | Utils.showBottomSheet(BuildContext context, Task task) { 21 | showModalBottomSheet( 22 | context: context, 23 | isScrollControlled: true, 24 | builder: (context) => BottomSheetContainer(task: task), 25 | ); 26 | } 27 | 28 | static DateTime timeToDateTime( 29 | TimeOfDay time, { 30 | year = 0, 31 | month = 0, 32 | day = 0, 33 | }) { 34 | return DateTime(year, month, day, time.hour, time.minute); 35 | } 36 | 37 | static String formatTime(TimeOfDay time) { 38 | return DateFormat.jm().format(timeToDateTime(time)); 39 | } 40 | 41 | static String dateToString(DateTime date) => date.toString(); 42 | 43 | static DateTime toDate(String date) => DateTime.parse(date); 44 | 45 | static String getHour(TimeOfDay time) { 46 | String hour = formatTime(time).split(":")[0]; 47 | return (hour.length == 1) ? '0$hour' : hour; 48 | } 49 | 50 | static TimeOfDay toTime(String time) { 51 | String hour = time.split(':')[0]; 52 | String rest = time.split(':')[1]; 53 | String minute = rest.split(' ')[0]; 54 | String period = rest.split(' ')[1]; 55 | 56 | if (period == 'PM') { 57 | int parsedHour = int.parse(hour) + 12; 58 | hour = parsedHour.toString(); 59 | } 60 | 61 | return TimeOfDay( 62 | hour: int.parse(hour), 63 | minute: int.parse(minute), 64 | ); 65 | } 66 | 67 | static String getMinutes(TimeOfDay time) => formatTime(time).split(":")[1]; 68 | 69 | Future selectDate( 70 | BuildContext context, 71 | TextEditingController date, 72 | ) async { 73 | DateTime selectedDate = DateTime.now(); 74 | 75 | final DateTime picked = await showDatePicker( 76 | context: context, 77 | initialDate: selectedDate, 78 | firstDate: selectedDate, 79 | lastDate: DateTime(2100), 80 | ); 81 | 82 | if (picked != null) { 83 | selectedDate = picked; 84 | date.value = TextEditingValue( 85 | text: dateToString(picked).substring(0, 10), 86 | ); 87 | } 88 | 89 | return null; 90 | } 91 | 92 | Future selectTime( 93 | BuildContext context, 94 | TextEditingController time, 95 | ) async { 96 | TimeOfDay selectedTime = TimeOfDay.now(); 97 | 98 | final TimeOfDay picked = await showTimePicker( 99 | context: context, 100 | initialTime: selectedTime, 101 | builder: (BuildContext context, Widget child) { 102 | return MediaQuery( 103 | data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: false), 104 | child: child, 105 | ); 106 | }, 107 | ); 108 | 109 | if (picked != null) { 110 | selectedTime = picked; 111 | time.value = TextEditingValue( 112 | text: formatTime(picked), 113 | ); 114 | } 115 | 116 | return ""; 117 | } 118 | 119 | static void showToast({@required String message, bool success = true}) { 120 | FlutterFlexibleToast.showToast( 121 | message: message, 122 | toastLength: Toast.LENGTH_LONG, 123 | toastGravity: ToastGravity.BOTTOM, 124 | icon: ICON.SUCCESS, 125 | radius: 10, 126 | elevation: 0, 127 | imageSize: 16, 128 | textColor: Colors.white, 129 | backgroundColor: success ? AppColors.greenAccent : Colors.red, 130 | timeInSeconds: 2, 131 | ); 132 | } 133 | 134 | static bool compareDate(DateTime date1, DateTime date2) { 135 | if (date2 != null) { 136 | return date1.day == date2.day && 137 | date1.month == date2.month && 138 | date1.year == date2.year; 139 | } 140 | 141 | return false; 142 | } 143 | 144 | static int randomNumber() { 145 | Random rng = new Random(); 146 | return rng.nextInt(900000000) + 100000000; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/routes/router.gr.dart'; 3 | import 'package:todo_app/services/auth_key.dart'; 4 | import 'package:todo_app/services/notification.dart'; 5 | import 'package:todo_app/views/pages/details/details.dart'; 6 | 7 | Reminder reminder = Reminder(); 8 | 9 | void main() async { 10 | WidgetsFlutterBinding.ensureInitialized(); 11 | await reminder.init(); 12 | runApp( 13 | MaterialApp( 14 | debugShowCheckedModeBanner: false, 15 | home: MyApp(), 16 | ), 17 | ); 18 | } 19 | 20 | class MyApp extends StatefulWidget { 21 | @override 22 | _MyAppState createState() => _MyAppState(); 23 | } 24 | 25 | class _MyAppState extends State { 26 | @override 27 | void initState() { 28 | reminder.requestIOSPermissions(); 29 | configureSelectNotificationSubject(); 30 | configureDidReceiveLocalNotificationSubject(); 31 | super.initState(); 32 | } 33 | 34 | void configureSelectNotificationSubject() { 35 | reminder.selectNotificationSubject.stream.listen( 36 | (String payload) async { 37 | await Navigator.push( 38 | context, 39 | MaterialPageRoute( 40 | builder: (context) => Details(id: payload), 41 | ), 42 | ); 43 | }, 44 | ); 45 | } 46 | 47 | void configureDidReceiveLocalNotificationSubject() { 48 | reminder.didReceiveLocalNotificationSubject.stream.listen( 49 | (ReceivedNotification receivedNotification) async { 50 | await Navigator.push( 51 | context, 52 | MaterialPageRoute( 53 | builder: (context) => Details(id: receivedNotification.payload), 54 | ), 55 | ); 56 | }, 57 | ); 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | AuthKeyService _authKey = AuthKeyService(); 63 | _authKey.setKey(); 64 | 65 | return MaterialApp( 66 | theme: ThemeData( 67 | canvasColor: Colors.transparent, 68 | ), 69 | debugShowCheckedModeBanner: false, 70 | initialRoute: Router.wrapperPage, 71 | onGenerateRoute: Router.onGenerateRoute, 72 | navigatorKey: Router.navigator.key, 73 | ); 74 | } 75 | 76 | @override 77 | void dispose() { 78 | reminder.dispose(); 79 | super.dispose(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/models/task.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | 5 | class Task { 6 | final String id; 7 | final String userId; 8 | final String title; 9 | final String note; 10 | final DateTime date; 11 | final TimeOfDay time; 12 | final bool isCompleted; 13 | final bool isReminderSet; 14 | final DateTime createdAt; 15 | final DateTime updatedAt; 16 | 17 | Task({ 18 | this.id = '', 19 | this.userId, 20 | this.title, 21 | this.note, 22 | this.date, 23 | this.time, 24 | this.isCompleted, 25 | this.isReminderSet, 26 | this.createdAt, 27 | this.updatedAt, 28 | }); 29 | 30 | Map toDocument() { 31 | return { 32 | 'userId': userId, 33 | 'title': title, 34 | 'note': note, 35 | 'date': Utils.dateToString(date), 36 | 'time': Utils.formatTime(time), 37 | 'isCompleted': isCompleted, 38 | 'isReminderSet': isReminderSet, 39 | 'createdAt': createdAt, 40 | 'updatedAt': updatedAt, 41 | }; 42 | } 43 | 44 | Task.fromSnapshot(DocumentSnapshot snap) 45 | : id = snap.documentID, 46 | userId = snap.data['userId'], 47 | title = snap.data['title'], 48 | note = snap.data['note'], 49 | date = Utils.toDate(snap.data['date']), 50 | time = Utils.toTime(snap.data['time']), 51 | isCompleted = snap.data['isCompleted'], 52 | isReminderSet = snap.data['isReminderSet'], 53 | createdAt = (snap.data['createdAt'] as Timestamp).toDate(), 54 | updatedAt = (snap.data['updatedAt'] as Timestamp).toDate(); 55 | } 56 | -------------------------------------------------------------------------------- /lib/routes/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:auto_route/auto_route.dart'; 2 | import 'package:auto_route/auto_route_annotations.dart'; 3 | import 'package:todo_app/views/pages/details/details.dart'; 4 | import 'package:todo_app/views/pages/wrapper.dart'; 5 | 6 | @MaterialAutoRouter() 7 | class $Router { 8 | @initial 9 | Wrapper wrapperPage; 10 | 11 | @CustomRoute( 12 | transitionsBuilder: TransitionsBuilders.slideLeftWithFade, 13 | ) 14 | Details detailsPage; 15 | } 16 | -------------------------------------------------------------------------------- /lib/routes/router.gr.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ************************************************************************** 4 | // AutoRouteGenerator 5 | // ************************************************************************** 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | import 'package:auto_route/auto_route.dart'; 10 | import 'package:todo_app/views/pages/wrapper.dart'; 11 | import 'package:todo_app/views/pages/details/details.dart'; 12 | import 'package:todo_app/models/task.dart'; 13 | 14 | class Router { 15 | static const wrapperPage = '/'; 16 | static const detailsPage = '/details-page'; 17 | static final navigator = ExtendedNavigator(); 18 | static Route onGenerateRoute(RouteSettings settings) { 19 | final args = settings.arguments; 20 | switch (settings.name) { 21 | case Router.wrapperPage: 22 | if (hasInvalidArgs(args)) { 23 | return misTypedArgsRoute(args); 24 | } 25 | final typedArgs = args as Key; 26 | return MaterialPageRoute( 27 | builder: (_) => Wrapper(key: typedArgs), 28 | settings: settings, 29 | ); 30 | case Router.detailsPage: 31 | if (hasInvalidArgs(args)) { 32 | return misTypedArgsRoute(args); 33 | } 34 | final typedArgs = args as DetailsArguments ?? DetailsArguments(); 35 | return PageRouteBuilder( 36 | pageBuilder: (ctx, animation, secondaryAnimation) => Details( 37 | key: typedArgs.key, id: typedArgs.id, task: typedArgs.task), 38 | settings: settings, 39 | transitionsBuilder: TransitionsBuilders.slideLeftWithFade, 40 | ); 41 | default: 42 | return unknownRoutePage(settings.name); 43 | } 44 | } 45 | } 46 | 47 | //************************************************************************** 48 | // Arguments holder classes 49 | //*************************************************************************** 50 | 51 | //Details arguments holder class 52 | class DetailsArguments { 53 | final Key key; 54 | final String id; 55 | final Task task; 56 | DetailsArguments({this.key, this.id, this.task}); 57 | } 58 | -------------------------------------------------------------------------------- /lib/services/auth_key.dart: -------------------------------------------------------------------------------- 1 | import 'package:localstorage/localstorage.dart'; 2 | import 'package:uuid/uuid.dart'; 3 | import 'package:validators/validators.dart'; 4 | 5 | class AuthKeyService { 6 | final Uuid uuid = Uuid(); 7 | final LocalStorage storage = new LocalStorage('auth_key'); 8 | 9 | /// generate a new uuid 10 | String _generateUuid() => uuid.v4(); 11 | 12 | /// Check if the key is a valid UUID 13 | bool isKeyUUID(String key) => (key != null) ? isUUID(key) : false; 14 | 15 | /// Read uuid from the local storage 16 | /// 17 | /// return NULL if a auth_key doesn't exist 18 | String getKey() { 19 | String authKey = 'auth_key'; 20 | String key = storage.getItem(authKey); 21 | return (this.isKeyUUID(key)) ? key : null; 22 | } 23 | 24 | /// Write a new uuid in the local storage 25 | /// 26 | /// If the storage doesn't already exist 27 | void setKey() { 28 | String value = getKey(); 29 | 30 | if (value == null) { 31 | String newAuthKey = this._generateUuid(); 32 | storage.setItem('auth_key', newAuthKey); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/services/firestore.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:todo_app/models/task.dart'; 3 | 4 | class FirestoreService { 5 | static final FirestoreService _firestoreService = FirestoreService._(); 6 | static final Firestore _db = Firestore.instance; 7 | 8 | FirestoreService._(); 9 | factory FirestoreService() => _firestoreService; 10 | 11 | CollectionReference collection = _db.collection('task'); 12 | 13 | Stream> getTaskByUser(String userId) { 14 | return collection 15 | .where("userId", isEqualTo: userId) 16 | .orderBy('updatedAt', descending: true) 17 | .snapshots() 18 | .map( 19 | (snapshot) => snapshot.documents.map( 20 | (doc) { 21 | return Task.fromSnapshot(doc); 22 | }, 23 | ).toList(), 24 | ); 25 | } 26 | 27 | Stream getTaskById(String id) => 28 | collection.document(id).snapshots(); 29 | 30 | Stream> getTaskByStatus(String userId, {bool isCompleted = true}) { 31 | return collection 32 | .where("userId", isEqualTo: userId) 33 | .where("isCompleted", isEqualTo: isCompleted) 34 | .orderBy('updatedAt', descending: true) 35 | .snapshots() 36 | .map( 37 | (snapshot) => snapshot.documents.map( 38 | (doc) { 39 | return Task.fromSnapshot(doc); 40 | }, 41 | ).toList(), 42 | ); 43 | } 44 | 45 | Future createTask(Task task) async { 46 | DocumentReference ref = await collection.add(task.toDocument()); 47 | return ref; 48 | } 49 | 50 | Future deleteTask(String id) async { 51 | DocumentReference ref = collection.document(id); 52 | await ref.delete(); 53 | return ref; 54 | } 55 | 56 | Future completeTask(String id, bool field) async { 57 | DocumentReference ref = collection.document(id); 58 | await ref.updateData({"isCompleted": field}); 59 | return ref; 60 | } 61 | 62 | Future setTaskReminder(String id, bool field) async { 63 | DocumentReference ref = collection.document(id); 64 | await ref.updateData({"isReminderSet": field}); 65 | return ref; 66 | } 67 | 68 | Future updateTask(String id, Task task) async { 69 | DocumentReference ref = collection.document(id); 70 | await ref.updateData(task.toDocument()); 71 | return ref; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/services/notification.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:rxdart/subjects.dart'; 4 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 5 | import 'package:todo_app/helpers/utils.dart'; 6 | import 'package:todo_app/models/task.dart'; 7 | 8 | class ReceivedNotification { 9 | final int id; 10 | final String title; 11 | final String body; 12 | final String payload; // the id of the task 13 | 14 | ReceivedNotification({ 15 | @required this.id, 16 | @required this.title, 17 | @required this.body, 18 | @required this.payload, 19 | }); 20 | } 21 | 22 | class Reminder { 23 | int notifId = Utils.randomNumber(); 24 | 25 | final FlutterLocalNotificationsPlugin notifPlugin = 26 | FlutterLocalNotificationsPlugin(); 27 | 28 | final BehaviorSubject 29 | didReceiveLocalNotificationSubject = 30 | BehaviorSubject(); 31 | 32 | final BehaviorSubject selectNotificationSubject = 33 | BehaviorSubject(); 34 | 35 | NotificationAppLaunchDetails notificationAppLaunchDetails; 36 | 37 | init() async { 38 | notificationAppLaunchDetails = 39 | await notifPlugin.getNotificationAppLaunchDetails(); 40 | 41 | var initializationSettingsAndroid = 42 | AndroidInitializationSettings('app_icon'); 43 | 44 | var initializationSettingsIOS = IOSInitializationSettings( 45 | requestAlertPermission: false, 46 | requestBadgePermission: false, 47 | requestSoundPermission: false, 48 | onDidReceiveLocalNotification: ( 49 | int id, 50 | String title, 51 | String body, 52 | String payload, 53 | ) async { 54 | didReceiveLocalNotificationSubject.add( 55 | ReceivedNotification( 56 | id: id, 57 | title: title, 58 | body: body, 59 | payload: payload, 60 | ), 61 | ); 62 | }, 63 | ); 64 | 65 | var initializationSettings = InitializationSettings( 66 | initializationSettingsAndroid, 67 | initializationSettingsIOS, 68 | ); 69 | 70 | await notifPlugin.initialize( 71 | initializationSettings, 72 | onSelectNotification: (dynamic payload) async { 73 | if (payload != null) { 74 | debugPrint('notification payload: ' + payload); 75 | } 76 | selectNotificationSubject.add(payload); 77 | }, 78 | ); 79 | } 80 | 81 | Future schedule( 82 | String taskId, 83 | String taskTitle, 84 | String taskNote, 85 | DateTime taskTime, 86 | ) async { 87 | DateTime now = DateTime.now(); 88 | DateTime scheduledTime = taskTime.subtract( 89 | Duration(minutes: 10), 90 | ); 91 | 92 | var vibrationPattern = Int64List(5); 93 | vibrationPattern[0] = 0; 94 | vibrationPattern[1] = 500; 95 | vibrationPattern[2] = 2000; 96 | vibrationPattern[3] = 5000; 97 | vibrationPattern[4] = 10000; 98 | 99 | var androidPlatformChannelSpecifics = AndroidNotificationDetails( 100 | 'task_manager_notification', 101 | 'task manager app', 102 | 'flutter task manager app notification', 103 | importance: Importance.Max, 104 | priority: Priority.High, 105 | sound: 'wecker_sound', 106 | vibrationPattern: vibrationPattern, 107 | enableLights: true, 108 | color: const Color.fromARGB(255, 255, 0, 0), 109 | ledColor: const Color.fromARGB(255, 255, 0, 0), 110 | ledOnMs: 1000, 111 | ledOffMs: 500, 112 | ); 113 | 114 | var iOSPlatformChannelSpecifics = IOSNotificationDetails( 115 | sound: 'wecker_sound.aiff', 116 | ); 117 | 118 | var platformChannelSpecifics = NotificationDetails( 119 | androidPlatformChannelSpecifics, 120 | iOSPlatformChannelSpecifics, 121 | ); 122 | 123 | // don't schedule the reminder 124 | // if the scheduled time is 5 minutes 125 | // before or a the same moment than now 126 | if (scheduledTime.isAtSameMomentAs(now) || 127 | scheduledTime.isBefore(now) || 128 | scheduledTime.difference(now).inMinutes <= 5 || 129 | taskTime.isAtSameMomentAs(now) || 130 | taskTime.isBefore(now)) { 131 | return null; 132 | } 133 | 134 | await notifPlugin.schedule( 135 | notifId, 136 | taskTitle, 137 | taskNote, 138 | scheduledTime, 139 | platformChannelSpecifics, 140 | androidAllowWhileIdle: true, 141 | payload: taskId, 142 | ); 143 | 144 | return notifId; 145 | } 146 | 147 | Future cancel(int notifId) async { 148 | await notifPlugin.cancel(notifId); 149 | } 150 | 151 | void requestIOSPermissions() { 152 | notifPlugin 153 | .resolvePlatformSpecificImplementation< 154 | IOSFlutterLocalNotificationsPlugin>() 155 | ?.requestPermissions( 156 | alert: true, 157 | badge: true, 158 | sound: true, 159 | ); 160 | } 161 | 162 | Future setup(String id, Task task) async { 163 | int year = task.date.year; 164 | int month = task.date.month; 165 | int day = task.date.day; 166 | TimeOfDay time = task.time; 167 | 168 | DateTime formatted = Utils.timeToDateTime( 169 | time, 170 | year: year, 171 | month: month, 172 | day: day, 173 | ); 174 | 175 | return await schedule( 176 | id, 177 | task.title, 178 | task.note, 179 | formatted, 180 | ); 181 | } 182 | 183 | void dispose() { 184 | didReceiveLocalNotificationSubject.close(); 185 | selectNotificationSubject.close(); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /lib/views/pages/details/completion_task.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 4 | import 'package:font_awesome_flutter/fa_icon.dart'; 5 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 6 | import 'package:todo_app/bloc/task_bloc.dart'; 7 | import 'package:todo_app/core/flushbar.dart'; 8 | import 'package:todo_app/helpers/colors.dart'; 9 | import 'package:todo_app/models/task.dart'; 10 | 11 | class TaskCompletionStatus extends StatelessWidget { 12 | final Task task; 13 | 14 | const TaskCompletionStatus({ 15 | Key key, 16 | @required this.task, 17 | }) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | String dialogTitle = 'Confirm'; 22 | String completeMsg = 'Do you want to mark the task as completed?'; 23 | String unCompleteMsg = 'Do you want to mark the task as uncompleted?'; 24 | return InkWell( 25 | onTap: () { 26 | completeTask( 27 | context, 28 | dialogTitle, 29 | completeMsg, 30 | unCompleteMsg, 31 | ).show(); 32 | }, 33 | highlightColor: Colors.grey[100], 34 | splashColor: Colors.transparent, 35 | child: Container( 36 | child: Row( 37 | children: [ 38 | Theme( 39 | data: ThemeData(accentColor: Colors.white), 40 | child: FloatingActionButton( 41 | mini: true, 42 | elevation: 0, 43 | heroTag: 'details-completed-task-${task.id}', 44 | onPressed: null, 45 | child: BlocBuilder( 46 | builder: (BuildContext context, TaskState state) { 47 | if (state is TaskCompleting) { 48 | return Center( 49 | child: SpinKitThreeBounce( 50 | color: AppColors.primary, 51 | size: 14, 52 | ), 53 | ); 54 | } 55 | 56 | return task.isCompleted 57 | ? FaIcon( 58 | FontAwesomeIcons.solidCheckCircle, 59 | color: AppColors.greenAccent, 60 | size: 20, 61 | ) 62 | : FaIcon( 63 | FontAwesomeIcons.toggleOn, 64 | color: Colors.grey[500], 65 | size: 20, 66 | ); 67 | }, 68 | ), 69 | ), 70 | ), 71 | Text( 72 | task.isCompleted ? 'Task completed' : 'Task uncompleted', 73 | style: TextStyle( 74 | fontFamily: 'Open Sans', 75 | color: AppColors.darkGrey, 76 | fontSize: 15, 77 | ), 78 | ), 79 | ], 80 | ), 81 | ), 82 | ); 83 | } 84 | 85 | AppFlushBar completeTask( 86 | BuildContext context, 87 | String dialogTitle, 88 | String completeMsg, 89 | String unCompleteMsg, 90 | ) { 91 | return AppFlushBar( 92 | onPressed: () { 93 | BlocProvider.of(context).add( 94 | CompleteTaskEvent(task.id, !task.isCompleted), 95 | ); 96 | }, 97 | padding: 16.2, 98 | context: context, 99 | title: dialogTitle, 100 | message: task.isCompleted ? unCompleteMsg : completeMsg, 101 | actionText: task.isCompleted ? 'Uncomplete' : 'Complete', 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/views/pages/details/details.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:todo_app/bloc/task_bloc.dart'; 4 | import 'package:todo_app/core/dialog.dart'; 5 | import 'package:todo_app/helpers/colors.dart'; 6 | import 'package:todo_app/helpers/utils.dart'; 7 | import 'package:todo_app/models/task.dart'; 8 | import 'package:todo_app/services/firestore.dart'; 9 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 10 | import 'package:todo_app/views/pages/details/completion_task.dart'; 11 | import 'package:todo_app/views/pages/details/reminder_status.dart'; 12 | import 'package:todo_app/views/pages/details/task_list.dart'; 13 | import 'package:todo_app/views/pages/wrapper.dart'; 14 | import 'package:todo_app/views/widgets/add_task_fb.dart'; 15 | import 'package:todo_app/views/widgets/day_task.dart'; 16 | 17 | class Details extends StatelessWidget { 18 | final String id; 19 | final Task task; 20 | 21 | Details({Key key, this.id, this.task}) : super(key: key); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | Task detailsTask; 26 | FirestoreService firestoreService = FirestoreService(); 27 | Stream taskStream = firestoreService.getTaskById(id ?? task.id); 28 | 29 | return Scaffold( 30 | backgroundColor: Colors.white, 31 | appBar: AppBar( 32 | title: Text('Task details'), 33 | backgroundColor: AppColors.primary, 34 | ), 35 | body: StreamBuilder( 36 | stream: taskStream, 37 | builder: (BuildContext context, AsyncSnapshot snapshot) { 38 | var taskData = snapshot.data; 39 | if (snapshot.hasError) { 40 | WidgetsBinding.instance.addPostFrameCallback( 41 | (_) => showDialog( 42 | context: context, 43 | builder: (_) => AppDialog( 44 | title: 'Network error', 45 | description: 46 | 'We are not able to load user data, at this moment...', 47 | onPressed: () { 48 | Navigator.of(context).pop(); 49 | return Wrapper(); 50 | }, 51 | ), 52 | ), 53 | ); 54 | } 55 | 56 | if (!snapshot.hasData) { 57 | return SpinKitThreeBounce(color: AppColors.primary, size: 30); 58 | } 59 | 60 | if (taskData != null) detailsTask = Task.fromSnapshot(taskData); 61 | 62 | return BlocProvider( 63 | create: (context) => TaskBloc(), 64 | child: BlocListener( 65 | listener: (BuildContext context, TaskState state) { 66 | if (state is TaskCompleted) { 67 | Utils.showToast(message: 'Task successfully updated'); 68 | } 69 | }, 70 | child: Container( 71 | padding: EdgeInsets.all(8.0), 72 | child: Column( 73 | children: [ 74 | Column( 75 | children: [ 76 | Container( 77 | margin: EdgeInsets.symmetric(vertical: 8.0), 78 | child: Row( 79 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 80 | children: [ 81 | DayTasksText(), 82 | AddTaskFloatingButton( 83 | task: taskData != null ? detailsTask : task, 84 | fromPage: TaskPageStatus.details, 85 | icon: Icons.edit, 86 | ), 87 | ], 88 | ), 89 | ), 90 | Divider(), 91 | Container( 92 | margin: EdgeInsets.symmetric(vertical: 8.0), 93 | child: Row( 94 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 95 | children: [ 96 | TaskCompletionStatus( 97 | task: taskData != null ? detailsTask : task, 98 | ), 99 | ], 100 | ), 101 | ), 102 | TaskReminderStatus( 103 | task: taskData != null ? detailsTask : task, 104 | ), 105 | Divider(), 106 | TaskList( 107 | task: taskData != null ? detailsTask : task, 108 | ) 109 | ], 110 | ), 111 | ], 112 | ), 113 | ), 114 | ), 115 | ); 116 | }, 117 | ), 118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/views/pages/details/reminder_status.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 4 | import 'package:font_awesome_flutter/fa_icon.dart'; 5 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 6 | import 'package:todo_app/bloc/task_bloc.dart'; 7 | import 'package:todo_app/core/flushbar.dart'; 8 | import 'package:todo_app/helpers/colors.dart'; 9 | import 'package:todo_app/models/task.dart'; 10 | 11 | class TaskReminderStatus extends StatelessWidget { 12 | final Task task; 13 | 14 | const TaskReminderStatus({ 15 | Key key, 16 | @required this.task, 17 | }) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | String dialogTitle = 'Confirm'; 22 | String unableReminderMsg = 'Do you want to unable reminder for the task?'; 23 | String disableReminderMsg = 'Do you want to disable reminder for the task?'; 24 | 25 | return InkWell( 26 | onTap: () { 27 | setTaskReminder( 28 | context, 29 | dialogTitle, 30 | unableReminderMsg, 31 | disableReminderMsg, 32 | ).show(); 33 | }, 34 | highlightColor: Colors.grey[100], 35 | splashColor: Colors.transparent, 36 | child: Container( 37 | margin: EdgeInsets.symmetric(vertical: 8.0), 38 | child: Row( 39 | children: [ 40 | Container( 41 | child: Row( 42 | children: [ 43 | Theme( 44 | data: ThemeData(accentColor: Colors.white), 45 | child: FloatingActionButton( 46 | heroTag: 'details-reminder-task-${task.id}', 47 | elevation: 0, 48 | onPressed: null, 49 | mini: true, 50 | child: BlocBuilder( 51 | builder: (BuildContext context, TaskState state) { 52 | if (state is TaskReminderSetting) { 53 | return Center( 54 | child: SpinKitThreeBounce( 55 | color: AppColors.primary, 56 | size: 14, 57 | ), 58 | ); 59 | } 60 | 61 | return task.isReminderSet 62 | ? FaIcon( 63 | FontAwesomeIcons.solidBell, 64 | color: AppColors.greenAccent, 65 | size: 20, 66 | ) 67 | : FaIcon( 68 | FontAwesomeIcons.solidBell, 69 | color: AppColors.darkGrey, 70 | size: 20, 71 | ); 72 | }, 73 | ), 74 | ), 75 | ), 76 | Text( 77 | task.isReminderSet 78 | ? 'Reminder unabled' 79 | : 'Reminder disabled', 80 | style: TextStyle( 81 | fontFamily: 'Open Sans', 82 | color: AppColors.darkGrey, 83 | fontSize: 15, 84 | ), 85 | ), 86 | ], 87 | ), 88 | ), 89 | ], 90 | ), 91 | ), 92 | ); 93 | } 94 | 95 | AppFlushBar setTaskReminder( 96 | BuildContext context, 97 | String dialogTitle, 98 | String unableReminderMsg, 99 | String disableReminderMsg, 100 | ) { 101 | return AppFlushBar( 102 | onPressed: () { 103 | BlocProvider.of(context).add( 104 | TaskReminderEvent(task, !task.isReminderSet), 105 | ); 106 | }, 107 | padding: 16.2, 108 | context: context, 109 | title: dialogTitle, 110 | message: task.isReminderSet ? disableReminderMsg : unableReminderMsg, 111 | actionText: task.isReminderSet ? 'Disable' : 'Unable', 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/views/pages/details/task_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TaskIcon extends StatelessWidget { 4 | final IconData icon; 5 | const TaskIcon({ 6 | Key key, 7 | @required this.icon, 8 | }) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | padding: EdgeInsets.all(12.0), 14 | decoration: BoxDecoration( 15 | border: Border.all(width: 0.1), 16 | borderRadius: BorderRadius.circular(30.0), 17 | ), 18 | child: Icon(icon), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/views/pages/details/task_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | import 'package:todo_app/models/task.dart'; 5 | import 'package:todo_app/views/pages/details/task_list_tile.dart'; 6 | 7 | class TaskList extends StatelessWidget { 8 | const TaskList({ 9 | Key key, 10 | @required this.task, 11 | }) : super(key: key); 12 | 13 | final Task task; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | margin: EdgeInsets.symmetric(vertical: 8.0, horizontal: 20.0), 19 | height: 420, 20 | child: ListView( 21 | children: ListTile.divideTiles( 22 | context: context, 23 | tiles: [ 24 | TaskListTile( 25 | title: 'Title', 26 | subTitle: task.title, 27 | leadingIcon: Icons.assignment, 28 | ), 29 | TaskListTile( 30 | title: 'Note', 31 | subTitle: task.note, 32 | leadingIcon: Icons.subject, 33 | ), 34 | TaskListTile( 35 | title: 'Date', 36 | subTitle: DateFormat('yyyy MMMM dd').format(task.date), 37 | leadingIcon: Icons.calendar_today, 38 | ), 39 | TaskListTile( 40 | title: 'Time', 41 | subTitle: Utils.formatTime(task.time), 42 | leadingIcon: Icons.alarm, 43 | ), 44 | ], 45 | ).toList(), 46 | ), // 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/views/pages/details/task_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:todo_app/views/pages/details/task_icon.dart'; 4 | 5 | class TaskListTile extends StatelessWidget { 6 | final String title; 7 | final String subTitle; 8 | final IconData leadingIcon; 9 | 10 | const TaskListTile({ 11 | Key key, 12 | @required this.title, 13 | @required this.subTitle, 14 | @required this.leadingIcon, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ListTile( 20 | leading: TaskIcon(icon: leadingIcon), 21 | title: Text( 22 | title, 23 | style: TextStyle( 24 | fontFamily: 'Open Sans', 25 | color: AppColors.darkGrey, 26 | fontSize: 12, 27 | ), 28 | ), 29 | subtitle: Text( 30 | subTitle, 31 | style: TextStyle( 32 | fontFamily: 'Open Sans', 33 | fontWeight: FontWeight.w300, 34 | fontSize: 15, 35 | ), 36 | ), 37 | contentPadding: EdgeInsets.all(8.0), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/views/pages/home/bottomsheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:todo_app/bloc/task_bloc.dart'; 4 | import 'package:todo_app/helpers/colors.dart'; 5 | import 'package:todo_app/models/task.dart'; 6 | import 'package:todo_app/views/widgets/forms/task_form.dart'; 7 | 8 | class BottomSheetContainer extends StatelessWidget { 9 | final Task task; 10 | const BottomSheetContainer({ 11 | Key key, 12 | this.task, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return SingleChildScrollView( 18 | child: Container( 19 | margin: EdgeInsets.only(bottom: 4, left: 8, right: 8), 20 | padding: EdgeInsets.only( 21 | bottom: MediaQuery.of(context).viewInsets.bottom, 22 | ), 23 | decoration: BoxDecoration( 24 | color: Colors.white, 25 | borderRadius: BorderRadius.all(Radius.circular(10)), 26 | ), 27 | child: Container( 28 | padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12.0), 29 | child: Column( 30 | children: [ 31 | Container( 32 | padding: const EdgeInsets.only(bottom: 2.0), 33 | child: Row( 34 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 35 | children: [ 36 | Flexible( 37 | child: Text( 38 | 'Task form', 39 | style: TextStyle( 40 | color: AppColors.darkGrey, 41 | fontSize: 20, 42 | fontFamily: 'Open Sans', 43 | fontWeight: FontWeight.w600, 44 | ), 45 | ), 46 | ), 47 | Flexible( 48 | child: InkWell( 49 | child: Theme( 50 | data: new ThemeData(accentColor: Colors.white), 51 | child: FloatingActionButton( 52 | heroTag: key, 53 | elevation: 0, 54 | mini: true, 55 | onPressed: () { 56 | Navigator.of(context).pop(); 57 | }, 58 | child: Icon( 59 | Icons.close, 60 | size: 20, 61 | color: AppColors.darkGrey, 62 | ), 63 | ), 64 | ), 65 | ), 66 | ) 67 | ], 68 | ), 69 | ), 70 | BlocProvider( 71 | create: (context) => TaskBloc(), 72 | child: TaskForm(task: task), 73 | ), 74 | ], 75 | ), 76 | ), 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/views/pages/home/btn_create_task.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 3 | import 'package:todo_app/helpers/colors.dart'; 4 | 5 | class BtnCreateTask extends StatelessWidget { 6 | final Function onPressed; 7 | const BtnCreateTask({ 8 | Key key, 9 | @required this.onPressed, 10 | }) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return FlatButton.icon( 15 | shape: RoundedRectangleBorder( 16 | borderRadius: BorderRadius.circular(4.0), 17 | ), 18 | padding: EdgeInsets.symmetric(vertical: 18, horizontal: 40), 19 | textColor: Colors.white, 20 | color: AppColors.primary, 21 | icon: FaIcon( 22 | FontAwesomeIcons.plus, 23 | size: 14, 24 | ), 25 | label: Text( 26 | 'New task', 27 | style: TextStyle( 28 | fontSize: 14, 29 | fontFamily: 'Open Sans', 30 | fontWeight: FontWeight.w300, 31 | ), 32 | ), 33 | onPressed: () { 34 | onPressed(context); 35 | }, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/views/pages/home/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/animation.dart'; 3 | import 'package:todo_app/helpers/colors.dart'; 4 | import 'package:todo_app/helpers/utils.dart'; 5 | import 'package:todo_app/views/pages/home/btn_create_task.dart'; 6 | import 'package:todo_app/views/widgets/illustration.dart'; 7 | 8 | class Home extends StatelessWidget { 9 | const Home({Key key}) : super(key: key); 10 | 11 | showCreateTaskModal(BuildContext context) { 12 | Utils.showBottomSheet(context, null); 13 | } 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | body: FadeAnimation( 19 | 0.4, 20 | child: Container( 21 | padding: EdgeInsets.all(30.0), 22 | color: AppColors.secondary, 23 | child: Column( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | Illustration(image: 'assets/images/undraw_ideas.svg'), 27 | TextGetStarted(), 28 | SizedBox(height: 40), 29 | BtnCreateTask(onPressed: showCreateTaskModal), 30 | ], 31 | ), 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | 38 | class TextGetStarted extends StatelessWidget { 39 | const TextGetStarted({ 40 | Key key, 41 | }) : super(key: key); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Text( 46 | 'To get started with the app, create your first task', 47 | textAlign: TextAlign.center, 48 | style: TextStyle( 49 | color: Colors.white, 50 | height: 1.4, 51 | fontSize: 20, 52 | fontFamily: 'Open Sans', 53 | fontWeight: FontWeight.w300, 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/views/pages/profile/active_task/active_task.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | import 'package:todo_app/services/firestore.dart'; 5 | import 'package:todo_app/views/widgets/add_task_fb.dart'; 6 | import 'package:todo_app/views/widgets/date_picker_timeline/date_picker_timeline.dart'; 7 | import 'package:todo_app/views/widgets/day_task.dart'; 8 | import 'package:todo_app/views/widgets/task_list.dart'; 9 | 10 | class ActiveTask extends StatefulWidget { 11 | final String title; 12 | 13 | const ActiveTask({Key key, @required this.title}) : super(key: key); 14 | 15 | @override 16 | _AllTaskState createState() => _AllTaskState(); 17 | } 18 | 19 | class _AllTaskState extends State { 20 | final pageStatus = TaskPageStatus.active; 21 | DateTime _selectedValue; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | Stream taskStream = FirestoreService().getTaskByStatus( 26 | 'c9ad90ca-7071-41c4-bb42-7945ea330a3a', 27 | isCompleted: false, 28 | ); 29 | 30 | return Scaffold( 31 | appBar: AppBar( 32 | title: Text(widget.title), 33 | backgroundColor: AppColors.primary, 34 | leading: Icon(Icons.event_note), 35 | ), 36 | body: Container( 37 | padding: EdgeInsets.all(8.0), 38 | child: Column( 39 | children: [ 40 | Column( 41 | children: [ 42 | DatePickerTimeline( 43 | _selectedValue, 44 | onDateChange: (date) { 45 | setState(() { 46 | _selectedValue = date; 47 | }); 48 | }, 49 | ), 50 | Divider(), 51 | ], 52 | ), 53 | Column( 54 | children: [ 55 | Container( 56 | margin: EdgeInsets.symmetric(vertical: 8.0), 57 | child: Row( 58 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 59 | children: [ 60 | DayTasksText(), 61 | AddTaskFloatingButton(fromPage: TaskPageStatus.active), 62 | ], 63 | ), 64 | ), 65 | Divider(), 66 | Container( 67 | height: MediaQuery.of(context).size.height - 68 | (7 * kBottomNavigationBarHeight), 69 | child: TaskList( 70 | taskStream: taskStream, 71 | pageStatus: pageStatus, 72 | ), 73 | ) 74 | ], 75 | ), 76 | ], 77 | ), 78 | ), 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/views/pages/profile/all_task/all_task.page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | import 'package:todo_app/services/firestore.dart'; 5 | import 'package:todo_app/views/widgets/add_task_fb.dart'; 6 | import 'package:todo_app/views/widgets/date_picker_timeline/date_picker_timeline.dart'; 7 | import 'package:todo_app/views/widgets/day_task.dart'; 8 | import 'package:todo_app/views/widgets/task_list.dart'; 9 | 10 | class AllTask extends StatefulWidget { 11 | final String title; 12 | 13 | const AllTask({Key key, @required this.title}) : super(key: key); 14 | 15 | @override 16 | _AllTaskState createState() => _AllTaskState(); 17 | } 18 | 19 | class _AllTaskState extends State { 20 | final pageStatus = TaskPageStatus.all; 21 | DateTime _selectedValue; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | Stream taskStream = FirestoreService().getTaskByUser( 26 | 'c9ad90ca-7071-41c4-bb42-7945ea330a3a', 27 | ); 28 | 29 | return Scaffold( 30 | appBar: AppBar( 31 | title: Text(widget.title), 32 | backgroundColor: AppColors.primary, 33 | leading: Icon(Icons.event_note), 34 | ), 35 | body: Container( 36 | padding: EdgeInsets.all(8.0), 37 | child: Column( 38 | children: [ 39 | Column( 40 | children: [ 41 | DatePickerTimeline( 42 | _selectedValue, 43 | onDateChange: (date) { 44 | setState(() { 45 | _selectedValue = date; 46 | }); 47 | }, 48 | ), 49 | Divider(), 50 | ], 51 | ), 52 | Column( 53 | children: [ 54 | Container( 55 | margin: EdgeInsets.symmetric(vertical: 8.0), 56 | child: Row( 57 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 58 | children: [ 59 | DayTasksText(), 60 | AddTaskFloatingButton(fromPage: TaskPageStatus.all), 61 | ], 62 | ), 63 | ), 64 | Divider(), 65 | Container( 66 | height: MediaQuery.of(context).size.height - 67 | (7 * kBottomNavigationBarHeight), 68 | child: TaskList( 69 | dateFilter: _selectedValue, 70 | taskStream: taskStream, 71 | pageStatus: pageStatus, 72 | ), 73 | ) 74 | ], 75 | ), 76 | ], 77 | ), 78 | ), 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/views/pages/profile/completed_task/completed_task.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | import 'package:todo_app/services/firestore.dart'; 5 | import 'package:todo_app/views/widgets/add_task_fb.dart'; 6 | import 'package:todo_app/views/widgets/date_picker_timeline/date_picker_timeline.dart'; 7 | import 'package:todo_app/views/widgets/day_task.dart'; 8 | import 'package:todo_app/views/widgets/task_list.dart'; 9 | 10 | class CompletedTask extends StatefulWidget { 11 | final String title; 12 | 13 | const CompletedTask({Key key, @required this.title}) : super(key: key); 14 | 15 | @override 16 | _AllTaskState createState() => _AllTaskState(); 17 | } 18 | 19 | class _AllTaskState extends State { 20 | final pageStatus = TaskPageStatus.completed; 21 | DateTime _selectedValue; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | Stream taskStream = FirestoreService().getTaskByStatus( 26 | 'c9ad90ca-7071-41c4-bb42-7945ea330a3a', 27 | ); 28 | 29 | return Scaffold( 30 | appBar: AppBar( 31 | title: Text(widget.title), 32 | backgroundColor: AppColors.primary, 33 | leading: Icon(Icons.event_note), 34 | ), 35 | body: Container( 36 | padding: EdgeInsets.all(8.0), 37 | child: Column( 38 | children: [ 39 | Column( 40 | children: [ 41 | DatePickerTimeline( 42 | _selectedValue, 43 | onDateChange: (date) { 44 | setState(() { 45 | _selectedValue = date; 46 | }); 47 | }, 48 | ), 49 | Divider(), 50 | ], 51 | ), 52 | Column( 53 | children: [ 54 | Container( 55 | margin: EdgeInsets.symmetric(vertical: 8.0), 56 | child: Row( 57 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 58 | children: [ 59 | DayTasksText(), 60 | AddTaskFloatingButton(fromPage: TaskPageStatus.completed), 61 | ], 62 | ), 63 | ), 64 | Divider(), 65 | Container( 66 | height: MediaQuery.of(context).size.height - 67 | (7 * kBottomNavigationBarHeight), 68 | child: TaskList( 69 | taskStream: taskStream, 70 | pageStatus: pageStatus, 71 | ), 72 | ) 73 | ], 74 | ), 75 | ], 76 | ), 77 | ), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/views/pages/profile/profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:ff_navigation_bar/ff_navigation_bar.dart'; 4 | import 'package:todo_app/views/pages/profile/active_task/active_task.dart'; 5 | import 'package:todo_app/views/pages/profile/all_task/all_task.page.dart'; 6 | import 'package:todo_app/views/pages/profile/completed_task/completed_task.dart'; 7 | 8 | class Profile extends StatefulWidget { 9 | const Profile({Key key}) : super(key: key); 10 | 11 | @override 12 | _ProfileState createState() => _ProfileState(); 13 | } 14 | 15 | class _ProfileState extends State { 16 | static int _currentPage = 0; 17 | GlobalKey _bottomNavigationKey = GlobalKey(); 18 | PageController _pageController; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _pageController = PageController( 24 | initialPage: _currentPage, 25 | ); 26 | } 27 | 28 | final List items = [ 29 | FFNavigationBarItem(iconData: Icons.event_note, label: "All"), 30 | FFNavigationBarItem(iconData: Icons.event_busy, label: "Active"), 31 | FFNavigationBarItem(iconData: Icons.event_available, label: "Completed"), 32 | ]; 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return Scaffold( 37 | body: PageView( 38 | controller: _pageController, 39 | children: [ 40 | AllTask(title: 'All tasks'), 41 | ActiveTask(title: 'Active tasks'), 42 | CompletedTask(title: 'Completed tasks'), 43 | ], 44 | onPageChanged: (int index) { 45 | setState(() { 46 | _currentPage = index; 47 | }); 48 | }, 49 | ), 50 | bottomNavigationBar: FFNavigationBar( 51 | key: _bottomNavigationKey, 52 | selectedIndex: _currentPage, 53 | theme: FFNavigationBarTheme( 54 | selectedItemIconColor: AppColors.primary, 55 | selectedItemBorderColor: AppColors.secondary, 56 | selectedItemBackgroundColor: AppColors.secondary, 57 | selectedItemLabelColor: AppColors.secondary, 58 | ), 59 | items: items, 60 | onSelectTab: (int index) { 61 | setState(() { 62 | _pageController.jumpToPage(index); 63 | _currentPage = index; 64 | }); 65 | }, 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/views/pages/wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/core/dialog.dart'; 3 | import 'package:todo_app/helpers/colors.dart'; 4 | // import 'package:todo_app/services/auth_key.dart'; 5 | import 'package:todo_app/services/firestore.dart'; 6 | import 'package:todo_app/views/pages/home/home.dart'; 7 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 8 | import 'package:todo_app/views/pages/profile/profile.dart'; 9 | 10 | class Wrapper extends StatelessWidget { 11 | const Wrapper({Key key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | // AuthKeyService _authKey = AuthKeyService(); 16 | // String authId = _authKey.getKey(); 17 | Stream taskStream = FirestoreService().getTaskByUser( 18 | 'c9ad90ca-7071-41c4-bb42-7945ea330a3a', 19 | ); 20 | 21 | return Scaffold( 22 | backgroundColor: Colors.white, 23 | body: StreamBuilder( 24 | stream: taskStream, 25 | builder: (BuildContext context, AsyncSnapshot snapshot) { 26 | var data = snapshot.data; 27 | if (snapshot.hasError) { 28 | WidgetsBinding.instance.addPostFrameCallback( 29 | (_) => showDialog( 30 | context: context, 31 | builder: (_) => AppDialog( 32 | title: 'Network error', 33 | description: 34 | 'We are not able to load user data, at this moment...', 35 | onPressed: () { 36 | Navigator.of(context).pop(); 37 | return Home(); 38 | }, 39 | ), 40 | ), 41 | ); 42 | } 43 | 44 | if (!snapshot.hasData) { 45 | return SpinKitThreeBounce(color: AppColors.primary, size: 30); 46 | } 47 | 48 | if (data.length == 0) return Home(); 49 | return Profile(); 50 | }, 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/views/widgets/add_task_fb.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | import 'package:todo_app/models/task.dart'; 5 | 6 | class AddTaskFloatingButton extends StatelessWidget { 7 | final IconData icon; 8 | final Task task; 9 | final TaskPageStatus fromPage; 10 | 11 | AddTaskFloatingButton({ 12 | Key key, 13 | this.task, 14 | this.icon = Icons.add, 15 | @required this.fromPage, 16 | }) : super(key: key); 17 | 18 | showCreateTaskModal(BuildContext context) { 19 | Utils.showBottomSheet(context, task); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Container( 25 | padding: EdgeInsets.symmetric(vertical: 8.0), 26 | child: FloatingActionButton( 27 | heroTag: key, 28 | backgroundColor: AppColors.primary, 29 | onPressed: () { 30 | showCreateTaskModal(context); 31 | }, 32 | tooltip: 33 | (fromPage == TaskPageStatus.details) ? 'Edit task' : 'Create task', 34 | child: Icon(icon, size: 30), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/views/widgets/date_picker_timeline/core/dimen.dart: -------------------------------------------------------------------------------- 1 | class Dimen { 2 | Dimen._(); 3 | 4 | static const double dateTextSize = 20; 5 | static const double dayTextSize = 9; 6 | static const double monthTextSize = 10; 7 | } 8 | -------------------------------------------------------------------------------- /lib/views/widgets/date_picker_timeline/core/style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:todo_app/views/widgets/date_picker_timeline/core/dimen.dart'; 4 | 5 | const TextStyle defaultMonthTextStyle = TextStyle( 6 | color: AppColors.darkGrey, 7 | fontSize: Dimen.monthTextSize, 8 | fontFamily: 'Open Sans', 9 | fontWeight: FontWeight.w600, 10 | ); 11 | 12 | const TextStyle defaultDateTextStyle = TextStyle( 13 | color: AppColors.dark, 14 | fontSize: Dimen.dateTextSize, 15 | fontFamily: 'Open Sans', 16 | fontWeight: FontWeight.w700, 17 | ); 18 | 19 | const TextStyle defaultDayTextStyle = TextStyle( 20 | color: AppColors.darkGrey, 21 | fontSize: Dimen.dayTextSize, 22 | fontFamily: 'Open Sans', 23 | fontWeight: FontWeight.w600, 24 | ); 25 | 26 | const TextStyle selectedMonthTextStyle = TextStyle( 27 | color: Colors.white, 28 | fontSize: Dimen.monthTextSize, 29 | fontFamily: 'Open Sans', 30 | fontWeight: FontWeight.w600, 31 | ); 32 | 33 | const TextStyle selectedDateTextStyle = TextStyle( 34 | color: Colors.white, 35 | fontSize: Dimen.dateTextSize, 36 | fontFamily: 'Open Sans', 37 | fontWeight: FontWeight.w700, 38 | ); 39 | 40 | const TextStyle selectedDayTextStyle = TextStyle( 41 | color: Colors.white, 42 | fontSize: Dimen.dayTextSize, 43 | fontFamily: 'Open Sans', 44 | fontWeight: FontWeight.w600, 45 | ); 46 | -------------------------------------------------------------------------------- /lib/views/widgets/date_picker_timeline/date_picker_timeline.dart: -------------------------------------------------------------------------------- 1 | library date_picker_timeline; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:intl/date_symbol_data_local.dart'; 5 | import 'package:todo_app/helpers/colors.dart'; 6 | import 'package:todo_app/helpers/utils.dart'; 7 | import 'package:todo_app/views/widgets/date_picker_timeline/date_widget.dart'; 8 | import 'package:todo_app/views/widgets/date_picker_timeline/core/style.dart'; 9 | import 'package:todo_app/views/widgets/date_picker_timeline/gestures/tap.dart'; 10 | 11 | class DatePickerTimeline extends StatefulWidget { 12 | DateTime currentDate; 13 | 14 | final double width; 15 | final double height; 16 | 17 | final TextStyle monthTextStyle, dayTextStyle, dateTextStyle; 18 | final Color selectionColor; 19 | final DateChangeListener onDateChange; 20 | final int daysCount; 21 | final String locale; 22 | 23 | // Creates the DatePickerTimeline Widget 24 | DatePickerTimeline( 25 | this.currentDate, { 26 | Key key, 27 | this.width, 28 | this.height = 60, 29 | this.monthTextStyle = defaultMonthTextStyle, 30 | this.dayTextStyle = defaultDayTextStyle, 31 | this.dateTextStyle = defaultDateTextStyle, 32 | this.selectionColor = AppColors.primary, 33 | this.daysCount = 50000, 34 | this.onDateChange, 35 | this.locale = "en_US", 36 | }) : super(key: key); 37 | 38 | @override 39 | State createState() => new _DatePickerState(); 40 | } 41 | 42 | class _DatePickerState extends State { 43 | @override 44 | void initState() { 45 | super.initState(); 46 | initializeDateFormatting(widget.locale, null); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Container( 52 | width: widget.width, 53 | height: widget.height, 54 | child: ListView.builder( 55 | itemCount: widget.daysCount, 56 | scrollDirection: Axis.horizontal, 57 | itemBuilder: (context, index) { 58 | // Return the Date Widget 59 | DateTime _date = DateTime.now().add(Duration(days: index)); 60 | DateTime date = DateTime(_date.year, _date.month, _date.day); 61 | bool isSelected = Utils.compareDate(date, widget.currentDate); 62 | 63 | return DateWidget( 64 | date: date, 65 | monthTextStyle: 66 | isSelected ? selectedMonthTextStyle : widget.monthTextStyle, 67 | dateTextStyle: 68 | isSelected ? selectedDateTextStyle : widget.dateTextStyle, 69 | dayTextStyle: 70 | isSelected ? selectedDayTextStyle : widget.dayTextStyle, 71 | locale: widget.locale, 72 | selectionColor: 73 | isSelected ? widget.selectionColor : Color(0x10000000), 74 | onDateSelected: (selectedDate) { 75 | // A date is selected 76 | if (widget.onDateChange != null) { 77 | widget.onDateChange(selectedDate); 78 | } 79 | setState(() { 80 | widget.currentDate = selectedDate; 81 | }); 82 | }, 83 | ); 84 | }, 85 | ), 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/views/widgets/date_picker_timeline/date_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:todo_app/views/widgets/date_picker_timeline/gestures/tap.dart'; 4 | 5 | class DateWidget extends StatelessWidget { 6 | final DateTime date; 7 | final TextStyle monthTextStyle, dayTextStyle, dateTextStyle; 8 | final Color selectionColor; 9 | final DateSelectionCallback onDateSelected; 10 | final String locale; 11 | 12 | DateWidget({ 13 | @required this.date, 14 | @required this.monthTextStyle, 15 | @required this.dayTextStyle, 16 | @required this.dateTextStyle, 17 | @required this.selectionColor, 18 | this.onDateSelected, 19 | this.locale, 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return InkWell( 25 | child: Container( 26 | margin: EdgeInsets.all(3.0), 27 | decoration: BoxDecoration( 28 | borderRadius: BorderRadius.all( 29 | Radius.circular(8.0), 30 | ), 31 | color: selectionColor, 32 | ), 33 | child: Padding( 34 | padding: const EdgeInsets.only( 35 | top: 2.0, 36 | bottom: 2.0, 37 | left: 15, 38 | right: 15, 39 | ), 40 | child: Column( 41 | mainAxisAlignment: MainAxisAlignment.center, 42 | crossAxisAlignment: CrossAxisAlignment.center, 43 | children: [ 44 | Text( 45 | DateFormat("MMM", locale).format(date).toUpperCase(), // Month 46 | style: monthTextStyle, 47 | ), 48 | Text( 49 | date.day.toString(), // Date 50 | style: dateTextStyle, 51 | ), 52 | Text( 53 | DateFormat("E", locale).format(date), // WeekDay 54 | style: dayTextStyle, 55 | ) 56 | ], 57 | ), 58 | ), 59 | ), 60 | onTap: () { 61 | if (onDateSelected != null) onDateSelected(this.date); 62 | }, 63 | onDoubleTap: () { 64 | if (onDateSelected != null) onDateSelected(null); 65 | }, 66 | onLongPress: () { 67 | if (onDateSelected != null) onDateSelected(null); 68 | }, 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/views/widgets/date_picker_timeline/gestures/tap.dart: -------------------------------------------------------------------------------- 1 | typedef DateSelectionCallback = void Function(DateTime selectedDate); 2 | 3 | typedef DateChangeListener = void Function(DateTime selectedDate); 4 | -------------------------------------------------------------------------------- /lib/views/widgets/day_task.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:flutter_svg/svg.dart'; 4 | import 'package:intl/intl.dart'; 5 | import 'package:todo_app/helpers/colors.dart'; 6 | import 'package:todo_app/models/task.dart'; 7 | import 'package:todo_app/services/firestore.dart'; 8 | 9 | class DayTasksText extends StatelessWidget { 10 | const DayTasksText({ 11 | Key key, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | Stream taskStream = FirestoreService().getTaskByUser( 17 | 'c9ad90ca-7071-41c4-bb42-7945ea330a3a', 18 | ); 19 | 20 | return StreamBuilder( 21 | stream: taskStream, 22 | builder: (BuildContext context, AsyncSnapshot snapshot) { 23 | List taskList = snapshot.data; 24 | int countTasks = snapshot.hasData 25 | ? taskList.where((item) => item.isCompleted == false).length 26 | : 0; 27 | 28 | return Row( 29 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 30 | children: [ 31 | Container( 32 | margin: EdgeInsets.only(right: 12.0), 33 | padding: EdgeInsets.all(16.0), 34 | decoration: BoxDecoration( 35 | color: AppColors.lightGrey, 36 | borderRadius: BorderRadius.circular(40.0), 37 | ), 38 | child: SizedBox( 39 | width: 30, 40 | height: 30, 41 | child: SvgPicture.asset( 42 | 'assets/images/to-do-list.svg', 43 | placeholderBuilder: (context) => SpinKitThreeBounce( 44 | color: AppColors.primary, 45 | size: 18, 46 | ), 47 | width: 30, 48 | height: 30, 49 | ), 50 | ), 51 | ), 52 | Container( 53 | child: Column( 54 | crossAxisAlignment: CrossAxisAlignment.start, 55 | mainAxisAlignment: MainAxisAlignment.center, 56 | children: [ 57 | RichText( 58 | text: TextSpan( 59 | text: '$countTasks', 60 | style: TextStyle( 61 | color: AppColors.dark, 62 | fontFamily: 'Open Sans', 63 | fontWeight: FontWeight.w200, 64 | fontSize: 38, 65 | ), 66 | children: [ 67 | TextSpan( 68 | text: (countTasks > 1) 69 | ? 'active tasks ' 70 | : 'active task ', 71 | style: TextStyle( 72 | fontWeight: FontWeight.w300, 73 | color: AppColors.darkGrey, 74 | fontSize: 10, 75 | ), 76 | ), 77 | ], 78 | ), 79 | ), 80 | Text( 81 | "Today ${DateFormat('yyyy MMMM dd').format(DateTime.now())}", 82 | style: TextStyle( 83 | fontWeight: FontWeight.w500, 84 | color: AppColors.darkGrey, 85 | fontSize: 14, 86 | ), 87 | ), 88 | ], 89 | ), 90 | ) 91 | ], 92 | ); 93 | }); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/views/widgets/forms/task_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 4 | import 'package:todo_app/bloc/task_bloc.dart'; 5 | import 'package:todo_app/core/custom_switch.dart'; 6 | import 'package:todo_app/core/validation.dart'; 7 | import 'package:todo_app/helpers/colors.dart'; 8 | import 'package:todo_app/helpers/utils.dart'; 9 | import 'package:todo_app/models/task.dart'; 10 | 11 | class TaskForm extends StatefulWidget { 12 | final Task task; 13 | 14 | const TaskForm({ 15 | Key key, 16 | this.task, 17 | }) : super(key: key); 18 | 19 | @override 20 | _TaskFormState createState() => _TaskFormState(); 21 | } 22 | 23 | class _TaskFormState extends State { 24 | bool _autoValidate = false; 25 | bool _isButtonDisabled = false; 26 | bool _isReminderSet; 27 | 28 | final _formKey = GlobalKey(); 29 | final Utils utils = Utils(); 30 | 31 | /// Text controllers 32 | TextEditingController _titleCtrl; 33 | TextEditingController _noteCtrl; 34 | TextEditingController _dateCtrl; 35 | TextEditingController _timeCtrl; 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | 41 | final Task task = widget.task; 42 | _isReminderSet = widget.task?.isReminderSet ?? false; 43 | 44 | _titleCtrl = TextEditingController(text: task == null ? '' : task.title); 45 | _noteCtrl = TextEditingController(text: task == null ? '' : task.note); 46 | _dateCtrl = TextEditingController( 47 | text: task == null ? '' : Utils.dateToString(task.date).substring(0, 10), 48 | ); 49 | _timeCtrl = TextEditingController( 50 | text: task == null ? '' : Utils.formatTime(task.time), 51 | ); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Form( 57 | key: _formKey, 58 | autovalidate: _autoValidate, 59 | child: Padding( 60 | padding: const EdgeInsets.symmetric(vertical: 2.0), 61 | child: Column( 62 | children: [ 63 | TextFormField( 64 | controller: _titleCtrl, 65 | validator: (value) => Validate.getMsg('title', value), 66 | decoration: buildInputDecoration( 67 | false, 68 | 'Enter the task title', 69 | iconData: Icons.edit, 70 | ), 71 | ), 72 | SizedBox(height: 10), 73 | Row( 74 | crossAxisAlignment: CrossAxisAlignment.start, 75 | children: [ 76 | Expanded( 77 | child: GestureDetector( 78 | onTap: () => utils.selectDate(context, _dateCtrl), 79 | child: AbsorbPointer( 80 | child: TextFormField( 81 | readOnly: true, 82 | controller: _dateCtrl, 83 | validator: (value) => Validate.getMsg( 84 | 'date', 85 | value, 86 | ), 87 | decoration: buildInputDecoration( 88 | false, 89 | 'Date', 90 | iconData: Icons.calendar_today, 91 | ), 92 | ), 93 | ), 94 | ), 95 | ), 96 | SizedBox(width: 5), 97 | Expanded( 98 | child: GestureDetector( 99 | onTap: () => utils.selectTime(context, _timeCtrl), 100 | child: AbsorbPointer( 101 | child: TextFormField( 102 | readOnly: true, 103 | controller: _timeCtrl, 104 | validator: (value) => Validate.getDateTimeMsg( 105 | 'time', 106 | value, 107 | ), 108 | decoration: buildInputDecoration( 109 | false, 110 | 'Time', 111 | iconData: Icons.alarm, 112 | ), 113 | ), 114 | ), 115 | ), 116 | ), 117 | ], 118 | ), 119 | SizedBox(height: 10), 120 | TextFormField( 121 | maxLines: 3, 122 | maxLength: 100, 123 | controller: _noteCtrl, 124 | validator: (value) => Validate.getMsg('note', value), 125 | decoration: buildInputDecoration( 126 | true, 127 | 'Enter a little note to describe the task', 128 | ), 129 | ), 130 | SizedBox(height: 10), 131 | Container( 132 | padding: EdgeInsets.symmetric(vertical: 8.0), 133 | child: Row( 134 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 135 | children: [ 136 | CustomSwitch( 137 | activeColor: AppColors.primary, 138 | value: widget.task == null 139 | ? _isReminderSet 140 | : widget.task.isReminderSet, 141 | onChanged: (value) { 142 | setState(() { 143 | _isReminderSet = value; 144 | }); 145 | }, 146 | ), 147 | Text( 148 | 'Set reminder for this task ?', 149 | style: TextStyle( 150 | fontFamily: 'Open Sans', 151 | color: AppColors.darkGrey, 152 | fontSize: 15, 153 | ), 154 | ), 155 | ], 156 | ), 157 | ), 158 | SizedBox(height: 10), 159 | Container( 160 | child: buildFlatButton(context, widget.task), 161 | ), 162 | ], 163 | ), 164 | ), 165 | ); 166 | } 167 | 168 | FlatButton buildFlatButton(BuildContext context, task) { 169 | return FlatButton( 170 | onPressed: _isButtonDisabled 171 | ? null 172 | : () { 173 | if (_formKey.currentState.validate()) { 174 | DateTime date = Utils.toDate(_dateCtrl.text); 175 | TimeOfDay time = Utils.toTime(_timeCtrl.text); 176 | 177 | Task newTask = Task( 178 | userId: 'c9ad90ca-7071-41c4-bb42-7945ea330a3a', 179 | title: _titleCtrl.text, 180 | note: _noteCtrl.text, 181 | date: date, 182 | time: time, 183 | isCompleted: task == null ? false : task.isCompleted, 184 | createdAt: task == null ? DateTime.now() : task.createdAt, 185 | updatedAt: DateTime.now(), 186 | isReminderSet: _isReminderSet, 187 | ); 188 | 189 | BlocProvider.of(context).add( 190 | task == null 191 | ? CreateTaskEvent(newTask) 192 | : UpdateTaskEvent(task.id, newTask), 193 | ); 194 | } else { 195 | setState(() => _autoValidate = true); 196 | } 197 | }, 198 | shape: RoundedRectangleBorder( 199 | borderRadius: BorderRadius.circular(4.0), 200 | ), 201 | padding: EdgeInsets.symmetric(vertical: 18, horizontal: 40), 202 | textColor: Colors.white, 203 | disabledColor: Colors.grey[200], 204 | color: AppColors.primary, 205 | child: BlocListener( 206 | listener: (BuildContext context, TaskState state) { 207 | if (state is TaskInitial || state is TaskLoading) { 208 | setState(() { 209 | _isButtonDisabled = true; 210 | }); 211 | } else if (state is TaskSubmitted) { 212 | Navigator.of(context).pop(); 213 | setState(() { 214 | _isButtonDisabled = false; 215 | }); 216 | 217 | Utils.showToast( 218 | message: task == null 219 | ? 'Task successfully created' 220 | : 'Task successfully updated', 221 | ); 222 | } 223 | }, 224 | child: SizedBox( 225 | width: double.infinity, 226 | child: BlocBuilder( 227 | builder: (BuildContext context, TaskState state) { 228 | if (state is TaskInitial || state is TaskSubmitted) { 229 | return Text( 230 | task == null ? 'Submit' : 'Update', 231 | textAlign: TextAlign.center, 232 | style: TextStyle( 233 | fontSize: 14, 234 | fontFamily: 'Open Sans', 235 | fontWeight: FontWeight.w300, 236 | ), 237 | ); 238 | } else { 239 | return Center( 240 | child: SpinKitThreeBounce( 241 | color: AppColors.primary, 242 | size: 14, 243 | ), 244 | ); 245 | } 246 | }, 247 | ), 248 | ), 249 | ), 250 | ); 251 | } 252 | 253 | InputDecoration buildInputDecoration(bool isTextArea, String hintText, 254 | {IconData iconData}) => 255 | InputDecoration( 256 | filled: true, 257 | hintText: hintText, 258 | fillColor: Colors.grey[100], 259 | prefixIcon: isTextArea ? null : Icon(iconData), 260 | contentPadding: isTextArea ? EdgeInsets.all(15.0) : null, 261 | enabledBorder: OutlineInputBorder( 262 | borderSide: BorderSide(color: Colors.transparent), 263 | ), 264 | focusedBorder: OutlineInputBorder( 265 | borderSide: BorderSide(color: Colors.transparent), 266 | ), 267 | errorBorder: OutlineInputBorder( 268 | borderSide: BorderSide(color: Colors.red), 269 | ), 270 | focusedErrorBorder: OutlineInputBorder( 271 | borderSide: BorderSide(color: Colors.red), 272 | ), 273 | ); 274 | } 275 | -------------------------------------------------------------------------------- /lib/views/widgets/illustration.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 3 | import 'package:flutter_svg/svg.dart'; 4 | import 'package:todo_app/helpers/colors.dart'; 5 | 6 | class Illustration extends StatelessWidget { 7 | final String image; 8 | 9 | /// image width 10 | final double width; 11 | 12 | /// image height 13 | final double height; 14 | 15 | const Illustration({ 16 | Key key, 17 | @required this.image, 18 | this.width = 300.0, 19 | this.height = 420.0, 20 | }) : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return SizedBox.fromSize( 25 | child: SvgPicture.asset( 26 | image, 27 | placeholderBuilder: (context) => SpinKitThreeBounce( 28 | color: AppColors.primary, 29 | size: 30, 30 | ), 31 | width: width, 32 | height: height, 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/views/widgets/no_data_illustration.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/views/widgets/illustration.dart'; 3 | 4 | class NoDataIllustration extends StatelessWidget { 5 | final String message; 6 | 7 | const NoDataIllustration({ 8 | Key key, 9 | this.message = 'No task found', 10 | }) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Center( 15 | child: Column( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | children: [ 18 | Illustration( 19 | image: 'assets/images/undraw_no_data.svg', 20 | width: 80, 21 | height: 180, 22 | ), 23 | Container( 24 | padding: const EdgeInsets.all(12.0), 25 | margin: const EdgeInsets.symmetric(horizontal: 30.0), 26 | child: Text( 27 | message, 28 | textAlign: TextAlign.center, 29 | style: TextStyle( 30 | color: Colors.grey, 31 | height: 1.4, 32 | fontSize: 18, 33 | fontFamily: 'Open Sans', 34 | fontWeight: FontWeight.w300, 35 | ), 36 | ), 37 | ) 38 | ], 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/views/widgets/task_card/card_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | 5 | class CardColor { 6 | final TaskPageStatus page; 7 | final bool isCompleted; 8 | 9 | CardColor(this.page, this.isCompleted); 10 | 11 | List get background { 12 | switch (page) { 13 | case TaskPageStatus.all: 14 | return isCompleted 15 | ? [AppColors.disabled, Colors.black45] 16 | : [AppColors.primaryAccent, AppColors.primaryLight]; 17 | break; 18 | case TaskPageStatus.completed: 19 | return [AppColors.disabled, AppColors.disabled]; 20 | break; 21 | default: 22 | return [AppColors.primarySoft, AppColors.primary]; 23 | break; 24 | } 25 | } 26 | 27 | Color get texts { 28 | if (page == TaskPageStatus.all && !isCompleted) { 29 | return Colors.white; 30 | } else if (isCompleted) { 31 | return Colors.black26; 32 | } 33 | return Colors.black45; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/views/widgets/task_card/custom_painter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CustomCardShapePainter extends CustomPainter { 5 | final double radius; 6 | final Color startColor; 7 | final Color endColor; 8 | 9 | CustomCardShapePainter(this.radius, this.startColor, this.endColor); 10 | 11 | @override 12 | void paint(Canvas canvas, Size size) { 13 | var paint = Paint(); 14 | paint.shader = ui.Gradient.linear( 15 | Offset(0, 0), Offset(size.width, size.height), [ 16 | HSLColor.fromColor(startColor).withLightness(0.8).toColor(), 17 | endColor 18 | ]); 19 | 20 | var path = Path() 21 | ..moveTo(0, size.height) 22 | ..lineTo(size.width - radius, size.height) 23 | ..quadraticBezierTo( 24 | size.width, size.height, size.width, size.height - radius) 25 | ..lineTo(size.width, radius) 26 | ..quadraticBezierTo(size.width, 0, size.width - radius, 0) 27 | ..lineTo(size.width - 1.5 * radius, 0) 28 | ..quadraticBezierTo(-radius, 2 * radius, 0, size.height) 29 | ..close(); 30 | 31 | canvas.drawPath(path, paint); 32 | } 33 | 34 | @override 35 | bool shouldRepaint(CustomPainter oldDelegate) => true; 36 | } 37 | -------------------------------------------------------------------------------- /lib/views/widgets/task_card/task_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 4 | import 'package:font_awesome_flutter/fa_icon.dart'; 5 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 6 | import 'package:todo_app/bloc/task_bloc.dart'; 7 | import 'package:todo_app/core/flushbar.dart'; 8 | import 'package:todo_app/helpers/colors.dart'; 9 | import 'package:todo_app/helpers/utils.dart'; 10 | import 'package:todo_app/models/task.dart'; 11 | import 'package:todo_app/views/widgets/task_card/card_color.dart'; 12 | import 'package:todo_app/views/widgets/task_card/custom_painter.dart'; 13 | import 'package:todo_app/views/widgets/task_card/text_info.dart'; 14 | import 'package:todo_app/views/widgets/task_card/time_board.dart'; 15 | 16 | class TaskCard extends StatefulWidget { 17 | final page; 18 | final Task task; 19 | final double borderRadius; 20 | 21 | const TaskCard({Key key, this.page, this.task, this.borderRadius = 10}) 22 | : super(key: key); 23 | 24 | @override 25 | _TaskCardState createState() => _TaskCardState(); 26 | } 27 | 28 | class _TaskCardState extends State { 29 | final double _height = 100; 30 | 31 | bool isSwitched = false; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | /// get the task fields 36 | String taskId = widget.task.id; 37 | bool isCompleted = widget.task.isCompleted; 38 | TimeOfDay time = widget.task.time; 39 | DateTime date = widget.task.date; 40 | 41 | String hour = Utils.getHour(time); 42 | String minute = Utils.getMinutes(time); 43 | 44 | /// get colors 45 | CardColor taskCardColors = CardColor(widget.page, isCompleted); 46 | List colors = taskCardColors.background; 47 | 48 | Widget buildTaskAction(bool isCompleted) { 49 | String dialogTitle = 'Confirm'; 50 | String dialogMsg = 'Do you want to delete this task?'; 51 | String completeMsg = 'Do you want to mark this task as completed?'; 52 | String unCompleteMsg = 'Do you want to mark this task as uncompleted?'; 53 | 54 | return BlocListener( 55 | listener: (BuildContext context, TaskState state) { 56 | if (state is TaskCompleted) { 57 | Utils.showToast(message: 'Task successfully updated'); 58 | } 59 | }, 60 | child: Container( 61 | child: Column( 62 | crossAxisAlignment: CrossAxisAlignment.end, 63 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 64 | children: [ 65 | Theme( 66 | data: ThemeData(accentColor: Colors.transparent), 67 | child: FloatingActionButton( 68 | heroTag: 'completed-task-$taskId', 69 | elevation: 0, 70 | mini: true, 71 | onPressed: () { 72 | AppFlushBar( 73 | onPressed: () { 74 | BlocProvider.of(context).add( 75 | CompleteTaskEvent(taskId, !isCompleted), 76 | ); 77 | }, 78 | padding: 16.2, 79 | context: context, 80 | title: dialogTitle, 81 | message: isCompleted ? unCompleteMsg : completeMsg, 82 | actionText: isCompleted ? 'Uncomplete' : 'Complete', 83 | ).show(); 84 | }, 85 | child: BlocBuilder( 86 | builder: (BuildContext context, TaskState state) { 87 | if (state is TaskCompleting) { 88 | return Center( 89 | child: SpinKitThreeBounce( 90 | color: Colors.white, 91 | size: 14, 92 | ), 93 | ); 94 | } 95 | 96 | return isCompleted 97 | ? FaIcon( 98 | FontAwesomeIcons.solidCheckCircle, 99 | color: AppColors.greenAccent, 100 | size: 18, 101 | ) 102 | : FaIcon( 103 | FontAwesomeIcons.toggleOn, 104 | color: Colors.grey[500], 105 | size: 18, 106 | ); 107 | }, 108 | ), 109 | ), 110 | ), 111 | Theme( 112 | data: ThemeData(accentColor: Colors.transparent), 113 | child: FloatingActionButton( 114 | heroTag: 'delete-task-$taskId', 115 | elevation: 0, 116 | mini: true, 117 | onPressed: () { 118 | AppFlushBar( 119 | onPressed: () { 120 | BlocProvider.of(context).add( 121 | DeleteTaskEvent(taskId), 122 | ); 123 | }, 124 | padding: 16.2, 125 | context: context, 126 | title: dialogTitle, 127 | message: dialogMsg, 128 | actionText: 'Delete', 129 | ).show(); 130 | }, 131 | child: FaIcon( 132 | FontAwesomeIcons.solidTrashAlt, 133 | color: taskCardColors.texts, 134 | size: 18, 135 | ), 136 | ), 137 | ) 138 | ], 139 | ), 140 | ), 141 | ); 142 | } 143 | 144 | LinearGradient buildLinearGradient(TaskPageStatus page, bool taskStatus) { 145 | if (page == TaskPageStatus.all && !taskStatus) { 146 | return LinearGradient( 147 | begin: Alignment.topLeft, 148 | end: Alignment.bottomRight, 149 | colors: [colors[0], colors[1]], 150 | ); 151 | } 152 | 153 | return LinearGradient(colors: [colors[0], colors[0]]); 154 | } 155 | 156 | return Stack( 157 | children: [ 158 | Container( 159 | height: _height, 160 | decoration: BoxDecoration( 161 | borderRadius: BorderRadius.circular(widget.borderRadius), 162 | gradient: buildLinearGradient(widget.page, isCompleted), 163 | ), 164 | ), 165 | Positioned( 166 | top: 0, 167 | right: 0, 168 | bottom: 0, 169 | child: Visibility( 170 | maintainSize: false, 171 | maintainState: true, 172 | maintainAnimation: true, 173 | visible: widget.page == TaskPageStatus.all && !isCompleted, 174 | child: CustomPaint( 175 | size: Size(82, _height), 176 | painter: CustomCardShapePainter( 177 | widget.borderRadius, 178 | colors[0], 179 | colors[1], 180 | ), 181 | ), 182 | ), 183 | ), 184 | Positioned.fill( 185 | child: Row( 186 | children: [ 187 | Expanded( 188 | flex: 3, 189 | child: TimeBoard( 190 | page: widget.page, 191 | isCompleted: isCompleted, 192 | hour: hour, 193 | minute: minute, 194 | ), 195 | ), 196 | Expanded( 197 | flex: 6, 198 | child: TextTaskInfo( 199 | page: widget.page, 200 | isCompleted: isCompleted, 201 | title: widget.task.title, 202 | note: widget.task.note, 203 | date: date, 204 | ), 205 | ), 206 | Expanded( 207 | flex: 2, 208 | child: buildTaskAction(isCompleted), 209 | ), 210 | ], 211 | ), 212 | ), 213 | ], 214 | ); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /lib/views/widgets/task_card/text_info.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | import 'package:todo_app/views/widgets/task_card/card_color.dart'; 5 | 6 | class TextTaskInfo extends StatelessWidget { 7 | final TaskPageStatus page; 8 | final String title; 9 | final String note; 10 | final DateTime date; 11 | final bool isCompleted; 12 | 13 | const TextTaskInfo({ 14 | Key key, 15 | @required this.page, 16 | @required this.title, 17 | @required this.note, 18 | @required this.date, 19 | @required this.isCompleted, 20 | }) : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | CardColor taskCardColors = CardColor(page, isCompleted); 25 | String dateFormat = DateFormat('yyyy MMMM dd').format(date); 26 | 27 | return Container( 28 | padding: const EdgeInsets.symmetric(vertical: 8.0), 29 | child: Column( 30 | mainAxisAlignment: MainAxisAlignment.spaceAround, 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | Container( 34 | padding: EdgeInsets.only(bottom: 2.0), 35 | child: Text( 36 | toBeginningOfSentenceCase(title), 37 | overflow: TextOverflow.ellipsis, 38 | style: TextStyle( 39 | fontSize: 16, 40 | fontFamily: 'Open Sans', 41 | color: taskCardColors.texts, 42 | fontWeight: FontWeight.w900, 43 | ), 44 | ), 45 | ), 46 | Container( 47 | child: Text( 48 | toBeginningOfSentenceCase(note), 49 | textAlign: TextAlign.justify, 50 | style: TextStyle( 51 | fontSize: 12, 52 | fontFamily: 'Open Sans', 53 | color: taskCardColors.texts, 54 | fontWeight: FontWeight.w400, 55 | ), 56 | ), 57 | ), 58 | Container( 59 | padding: EdgeInsets.only(top: 4.0), 60 | child: Text( 61 | dateFormat, 62 | textAlign: TextAlign.start, 63 | style: TextStyle( 64 | fontSize: 10, 65 | fontFamily: 'Open Sans', 66 | color: taskCardColors.texts, 67 | fontWeight: FontWeight.w300, 68 | ), 69 | ), 70 | ) 71 | ], 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/views/widgets/task_card/time_board.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:todo_app/helpers/colors.dart'; 3 | import 'package:todo_app/helpers/utils.dart'; 4 | 5 | class TimeBoard extends StatelessWidget { 6 | final String hour; 7 | final String minute; // should contains PM or AM 8 | final page; 9 | final bool isCompleted; 10 | 11 | const TimeBoard({ 12 | Key key, 13 | this.hour, 14 | this.minute, 15 | this.page, 16 | this.isCompleted, 17 | }) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Container( 22 | margin: EdgeInsets.all(12.0), 23 | decoration: BoxDecoration( 24 | borderRadius: BorderRadius.all(Radius.circular(10.0)), 25 | color: 26 | (page == TaskPageStatus.active) ? AppColors.primary : Colors.white, 27 | ), 28 | child: Column( 29 | mainAxisAlignment: MainAxisAlignment.center, 30 | children: [ 31 | Text( 32 | '$hour', 33 | textAlign: TextAlign.center, 34 | style: TextStyle( 35 | fontFamily: 'Open Sans', 36 | fontSize: 40, 37 | fontWeight: FontWeight.w900, 38 | color: (page == TaskPageStatus.active) 39 | ? Colors.white 40 | : Colors.black45, 41 | ), 42 | ), 43 | Text( 44 | '$minute', 45 | textAlign: TextAlign.center, 46 | style: TextStyle( 47 | fontFamily: 'Open Sans', 48 | fontSize: 12, 49 | fontWeight: FontWeight.w400, 50 | color: (page == TaskPageStatus.active) 51 | ? Colors.white 52 | : Colors.black45, 53 | ), 54 | ) 55 | ], 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/views/widgets/task_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_spinkit/flutter_spinkit.dart'; 4 | import 'package:intl/intl.dart'; 5 | import 'package:todo_app/bloc/task_bloc.dart'; 6 | import 'package:todo_app/core/dialog.dart'; 7 | import 'package:todo_app/helpers/animation.dart'; 8 | import 'package:todo_app/helpers/colors.dart'; 9 | import 'package:todo_app/helpers/utils.dart'; 10 | import 'package:todo_app/models/task.dart'; 11 | import 'package:todo_app/routes/router.gr.dart'; 12 | import 'package:todo_app/views/pages/home/home.dart'; 13 | import 'package:todo_app/views/widgets/no_data_illustration.dart'; 14 | import 'package:todo_app/views/widgets/task_card/task_card.dart'; 15 | 16 | class TaskList extends StatelessWidget { 17 | const TaskList({ 18 | Key key, 19 | this.dateFilter, 20 | @required this.taskStream, 21 | @required this.pageStatus, 22 | }) : super(key: key); 23 | 24 | final double _borderRadius = 10; 25 | final Stream taskStream; 26 | final DateTime dateFilter; 27 | final TaskPageStatus pageStatus; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return StreamBuilder( 32 | stream: taskStream, 33 | builder: (BuildContext context, AsyncSnapshot snapshot) { 34 | List taskList = snapshot.data; 35 | 36 | if (snapshot.hasError) { 37 | WidgetsBinding.instance.addPostFrameCallback( 38 | (_) => showDialog( 39 | context: context, 40 | builder: (_) => AppDialog( 41 | title: 'Network error', 42 | description: 43 | 'We are not able to load user data, at this moment...', 44 | onPressed: () { 45 | Navigator.of(context).pop(); 46 | return Home(); 47 | }, 48 | ), 49 | ), 50 | ); 51 | } 52 | 53 | if (!snapshot.hasData) { 54 | return Center( 55 | child: SpinKitThreeBounce( 56 | color: AppColors.primary, 57 | size: 30, 58 | ), 59 | ); 60 | } 61 | 62 | if (taskList.length == 0) { 63 | switch (pageStatus) { 64 | case TaskPageStatus.all: 65 | return Home(); 66 | break; 67 | 68 | default: 69 | return NoDataIllustration(); 70 | } 71 | } 72 | 73 | // filter 74 | if (dateFilter != null) { 75 | List filteredTask = taskList.where((item) { 76 | return Utils.compareDate(item.date, dateFilter); 77 | }).toList(); 78 | 79 | String formattedDate = DateFormat('yyyy MMM dd').format(dateFilter); 80 | 81 | if (filteredTask.length == 0) { 82 | return NoDataIllustration( 83 | message: 'No task scheduled for $formattedDate was found', 84 | ); 85 | } 86 | 87 | taskList = filteredTask; 88 | } 89 | 90 | return ListView.builder( 91 | scrollDirection: Axis.vertical, 92 | itemCount: taskList.length, 93 | itemBuilder: (context, index) { 94 | return Padding( 95 | padding: const EdgeInsets.only(bottom: 12.0), 96 | child: BlocProvider( 97 | create: (context) => TaskBloc(), 98 | child: FadeAnimation( 99 | 0.4, 100 | child: InkWell( 101 | borderRadius: BorderRadius.circular(_borderRadius), 102 | onTap: () { 103 | Task task = taskList[index]; 104 | Navigator.of(context).pushNamed( 105 | Router.detailsPage, 106 | arguments: DetailsArguments(task: task), 107 | ); 108 | }, 109 | child: TaskCard( 110 | borderRadius: _borderRadius, 111 | task: taskList[index], 112 | page: pageStatus, 113 | ), 114 | ), 115 | ), 116 | ), 117 | ); 118 | }, 119 | ); 120 | }, 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.3" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "0.39.4" 18 | archive: 19 | dependency: transitive 20 | description: 21 | name: archive 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.11" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.5.2" 32 | async: 33 | dependency: transitive 34 | description: 35 | name: async 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.4.0" 39 | auto_route: 40 | dependency: "direct main" 41 | description: 42 | name: auto_route 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "0.3.1" 46 | auto_route_generator: 47 | dependency: "direct dev" 48 | description: 49 | name: auto_route_generator 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "0.3.2" 53 | bloc: 54 | dependency: transitive 55 | description: 56 | name: bloc 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "3.0.0" 60 | boolean_selector: 61 | dependency: transitive 62 | description: 63 | name: boolean_selector 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.0.5" 67 | build: 68 | dependency: transitive 69 | description: 70 | name: build 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.2.2" 74 | build_config: 75 | dependency: transitive 76 | description: 77 | name: build_config 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "0.4.2" 81 | build_daemon: 82 | dependency: transitive 83 | description: 84 | name: build_daemon 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "2.1.3" 88 | build_resolvers: 89 | dependency: transitive 90 | description: 91 | name: build_resolvers 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.3.3" 95 | build_runner: 96 | dependency: "direct dev" 97 | description: 98 | name: build_runner 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.8.0" 102 | build_runner_core: 103 | dependency: transitive 104 | description: 105 | name: build_runner_core 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "4.5.2" 109 | built_collection: 110 | dependency: transitive 111 | description: 112 | name: built_collection 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "4.3.2" 116 | built_value: 117 | dependency: transitive 118 | description: 119 | name: built_value 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "7.0.9" 123 | charcode: 124 | dependency: transitive 125 | description: 126 | name: charcode 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.1.2" 130 | checked_yaml: 131 | dependency: transitive 132 | description: 133 | name: checked_yaml 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "1.0.2" 137 | cloud_firestore: 138 | dependency: "direct main" 139 | description: 140 | name: cloud_firestore 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "0.13.4+2" 144 | cloud_firestore_platform_interface: 145 | dependency: transitive 146 | description: 147 | name: cloud_firestore_platform_interface 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "1.1.0" 151 | cloud_firestore_web: 152 | dependency: transitive 153 | description: 154 | name: cloud_firestore_web 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "0.1.1" 158 | code_builder: 159 | dependency: transitive 160 | description: 161 | name: code_builder 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "3.2.1" 165 | collection: 166 | dependency: transitive 167 | description: 168 | name: collection 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.14.11" 172 | convert: 173 | dependency: transitive 174 | description: 175 | name: convert 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "2.1.1" 179 | crypto: 180 | dependency: transitive 181 | description: 182 | name: crypto 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "2.1.3" 186 | csslib: 187 | dependency: transitive 188 | description: 189 | name: csslib 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "0.16.1" 193 | cupertino_icons: 194 | dependency: "direct main" 195 | description: 196 | name: cupertino_icons 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "0.1.3" 200 | dart_style: 201 | dependency: transitive 202 | description: 203 | name: dart_style 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "1.3.3" 207 | equatable: 208 | dependency: "direct main" 209 | description: 210 | name: equatable 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.1.0" 214 | ff_navigation_bar: 215 | dependency: "direct main" 216 | description: 217 | name: ff_navigation_bar 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "0.1.5" 221 | firebase: 222 | dependency: transitive 223 | description: 224 | name: firebase 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "7.2.1" 228 | firebase_core: 229 | dependency: transitive 230 | description: 231 | name: firebase_core 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "0.4.4" 235 | firebase_core_platform_interface: 236 | dependency: transitive 237 | description: 238 | name: firebase_core_platform_interface 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "1.0.4" 242 | firebase_core_web: 243 | dependency: transitive 244 | description: 245 | name: firebase_core_web 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "0.1.1+2" 249 | fixnum: 250 | dependency: transitive 251 | description: 252 | name: fixnum 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "0.10.11" 256 | flushbar: 257 | dependency: "direct main" 258 | description: 259 | name: flushbar 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.9.1" 263 | flutter: 264 | dependency: "direct main" 265 | description: flutter 266 | source: sdk 267 | version: "0.0.0" 268 | flutter_bloc: 269 | dependency: "direct main" 270 | description: 271 | name: flutter_bloc 272 | url: "https://pub.dartlang.org" 273 | source: hosted 274 | version: "3.2.0" 275 | flutter_flexible_toast: 276 | dependency: "direct main" 277 | description: 278 | name: flutter_flexible_toast 279 | url: "https://pub.dartlang.org" 280 | source: hosted 281 | version: "0.1.4" 282 | flutter_local_notifications: 283 | dependency: "direct main" 284 | description: 285 | name: flutter_local_notifications 286 | url: "https://pub.dartlang.org" 287 | source: hosted 288 | version: "1.2.2" 289 | flutter_local_notifications_platform_interface: 290 | dependency: transitive 291 | description: 292 | name: flutter_local_notifications_platform_interface 293 | url: "https://pub.dartlang.org" 294 | source: hosted 295 | version: "1.0.1" 296 | flutter_spinkit: 297 | dependency: "direct main" 298 | description: 299 | name: flutter_spinkit 300 | url: "https://pub.dartlang.org" 301 | source: hosted 302 | version: "4.1.2" 303 | flutter_svg: 304 | dependency: "direct main" 305 | description: 306 | name: flutter_svg 307 | url: "https://pub.dartlang.org" 308 | source: hosted 309 | version: "0.17.1" 310 | flutter_test: 311 | dependency: "direct dev" 312 | description: flutter 313 | source: sdk 314 | version: "0.0.0" 315 | flutter_web_plugins: 316 | dependency: transitive 317 | description: flutter 318 | source: sdk 319 | version: "0.0.0" 320 | font_awesome_flutter: 321 | dependency: "direct main" 322 | description: 323 | name: font_awesome_flutter 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "8.7.0" 327 | glob: 328 | dependency: transitive 329 | description: 330 | name: glob 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "1.2.0" 334 | graphs: 335 | dependency: transitive 336 | description: 337 | name: graphs 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "0.2.0" 341 | html: 342 | dependency: transitive 343 | description: 344 | name: html 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "0.14.0+3" 348 | http: 349 | dependency: transitive 350 | description: 351 | name: http 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "0.12.0+4" 355 | http_multi_server: 356 | dependency: transitive 357 | description: 358 | name: http_multi_server 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "2.2.0" 362 | http_parser: 363 | dependency: transitive 364 | description: 365 | name: http_parser 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "3.1.3" 369 | image: 370 | dependency: transitive 371 | description: 372 | name: image 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "2.1.4" 376 | intl: 377 | dependency: "direct main" 378 | description: 379 | name: intl 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "0.16.1" 383 | io: 384 | dependency: transitive 385 | description: 386 | name: io 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "0.3.3" 390 | js: 391 | dependency: transitive 392 | description: 393 | name: js 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "0.6.1+1" 397 | json_annotation: 398 | dependency: transitive 399 | description: 400 | name: json_annotation 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "3.0.1" 404 | localstorage: 405 | dependency: "direct main" 406 | description: 407 | name: localstorage 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "3.0.1+4" 411 | logging: 412 | dependency: transitive 413 | description: 414 | name: logging 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "0.11.4" 418 | matcher: 419 | dependency: transitive 420 | description: 421 | name: matcher 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "0.12.6" 425 | meta: 426 | dependency: transitive 427 | description: 428 | name: meta 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "1.1.8" 432 | mime: 433 | dependency: transitive 434 | description: 435 | name: mime 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "0.9.6+3" 439 | nested: 440 | dependency: transitive 441 | description: 442 | name: nested 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "0.0.4" 446 | node_interop: 447 | dependency: transitive 448 | description: 449 | name: node_interop 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "1.0.3" 453 | node_io: 454 | dependency: transitive 455 | description: 456 | name: node_io 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "1.0.1+2" 460 | package_config: 461 | dependency: transitive 462 | description: 463 | name: package_config 464 | url: "https://pub.dartlang.org" 465 | source: hosted 466 | version: "1.9.1" 467 | package_resolver: 468 | dependency: transitive 469 | description: 470 | name: package_resolver 471 | url: "https://pub.dartlang.org" 472 | source: hosted 473 | version: "1.0.10" 474 | path: 475 | dependency: transitive 476 | description: 477 | name: path 478 | url: "https://pub.dartlang.org" 479 | source: hosted 480 | version: "1.6.4" 481 | path_drawing: 482 | dependency: transitive 483 | description: 484 | name: path_drawing 485 | url: "https://pub.dartlang.org" 486 | source: hosted 487 | version: "0.4.1" 488 | path_parsing: 489 | dependency: transitive 490 | description: 491 | name: path_parsing 492 | url: "https://pub.dartlang.org" 493 | source: hosted 494 | version: "0.1.4" 495 | path_provider: 496 | dependency: transitive 497 | description: 498 | name: path_provider 499 | url: "https://pub.dartlang.org" 500 | source: hosted 501 | version: "1.4.4" 502 | pedantic: 503 | dependency: transitive 504 | description: 505 | name: pedantic 506 | url: "https://pub.dartlang.org" 507 | source: hosted 508 | version: "1.8.0+1" 509 | petitparser: 510 | dependency: transitive 511 | description: 512 | name: petitparser 513 | url: "https://pub.dartlang.org" 514 | source: hosted 515 | version: "2.4.0" 516 | platform: 517 | dependency: transitive 518 | description: 519 | name: platform 520 | url: "https://pub.dartlang.org" 521 | source: hosted 522 | version: "2.2.1" 523 | plugin_platform_interface: 524 | dependency: transitive 525 | description: 526 | name: plugin_platform_interface 527 | url: "https://pub.dartlang.org" 528 | source: hosted 529 | version: "1.0.2" 530 | pool: 531 | dependency: transitive 532 | description: 533 | name: pool 534 | url: "https://pub.dartlang.org" 535 | source: hosted 536 | version: "1.4.0" 537 | provider: 538 | dependency: transitive 539 | description: 540 | name: provider 541 | url: "https://pub.dartlang.org" 542 | source: hosted 543 | version: "4.0.4" 544 | pub_semver: 545 | dependency: transitive 546 | description: 547 | name: pub_semver 548 | url: "https://pub.dartlang.org" 549 | source: hosted 550 | version: "1.4.3" 551 | pubspec_parse: 552 | dependency: transitive 553 | description: 554 | name: pubspec_parse 555 | url: "https://pub.dartlang.org" 556 | source: hosted 557 | version: "0.1.5" 558 | quiver: 559 | dependency: transitive 560 | description: 561 | name: quiver 562 | url: "https://pub.dartlang.org" 563 | source: hosted 564 | version: "2.0.5" 565 | rxdart: 566 | dependency: transitive 567 | description: 568 | name: rxdart 569 | url: "https://pub.dartlang.org" 570 | source: hosted 571 | version: "0.23.1" 572 | shelf: 573 | dependency: transitive 574 | description: 575 | name: shelf 576 | url: "https://pub.dartlang.org" 577 | source: hosted 578 | version: "0.7.5" 579 | shelf_web_socket: 580 | dependency: transitive 581 | description: 582 | name: shelf_web_socket 583 | url: "https://pub.dartlang.org" 584 | source: hosted 585 | version: "0.2.3" 586 | simple_animations: 587 | dependency: "direct main" 588 | description: 589 | name: simple_animations 590 | url: "https://pub.dartlang.org" 591 | source: hosted 592 | version: "1.3.8" 593 | sky_engine: 594 | dependency: transitive 595 | description: flutter 596 | source: sdk 597 | version: "0.0.99" 598 | source_gen: 599 | dependency: transitive 600 | description: 601 | name: source_gen 602 | url: "https://pub.dartlang.org" 603 | source: hosted 604 | version: "0.9.5" 605 | source_span: 606 | dependency: transitive 607 | description: 608 | name: source_span 609 | url: "https://pub.dartlang.org" 610 | source: hosted 611 | version: "1.5.5" 612 | stack_trace: 613 | dependency: transitive 614 | description: 615 | name: stack_trace 616 | url: "https://pub.dartlang.org" 617 | source: hosted 618 | version: "1.9.3" 619 | stream_channel: 620 | dependency: transitive 621 | description: 622 | name: stream_channel 623 | url: "https://pub.dartlang.org" 624 | source: hosted 625 | version: "2.0.0" 626 | stream_transform: 627 | dependency: transitive 628 | description: 629 | name: stream_transform 630 | url: "https://pub.dartlang.org" 631 | source: hosted 632 | version: "1.2.0" 633 | string_scanner: 634 | dependency: transitive 635 | description: 636 | name: string_scanner 637 | url: "https://pub.dartlang.org" 638 | source: hosted 639 | version: "1.0.5" 640 | term_glyph: 641 | dependency: transitive 642 | description: 643 | name: term_glyph 644 | url: "https://pub.dartlang.org" 645 | source: hosted 646 | version: "1.1.0" 647 | test_api: 648 | dependency: transitive 649 | description: 650 | name: test_api 651 | url: "https://pub.dartlang.org" 652 | source: hosted 653 | version: "0.2.11" 654 | timing: 655 | dependency: transitive 656 | description: 657 | name: timing 658 | url: "https://pub.dartlang.org" 659 | source: hosted 660 | version: "0.1.1+2" 661 | typed_data: 662 | dependency: transitive 663 | description: 664 | name: typed_data 665 | url: "https://pub.dartlang.org" 666 | source: hosted 667 | version: "1.1.6" 668 | uuid: 669 | dependency: "direct main" 670 | description: 671 | name: uuid 672 | url: "https://pub.dartlang.org" 673 | source: hosted 674 | version: "2.0.4" 675 | validators: 676 | dependency: "direct main" 677 | description: 678 | name: validators 679 | url: "https://pub.dartlang.org" 680 | source: hosted 681 | version: "2.0.0+1" 682 | vector_math: 683 | dependency: transitive 684 | description: 685 | name: vector_math 686 | url: "https://pub.dartlang.org" 687 | source: hosted 688 | version: "2.0.8" 689 | watcher: 690 | dependency: transitive 691 | description: 692 | name: watcher 693 | url: "https://pub.dartlang.org" 694 | source: hosted 695 | version: "0.9.7+14" 696 | web_socket_channel: 697 | dependency: transitive 698 | description: 699 | name: web_socket_channel 700 | url: "https://pub.dartlang.org" 701 | source: hosted 702 | version: "1.1.0" 703 | xml: 704 | dependency: transitive 705 | description: 706 | name: xml 707 | url: "https://pub.dartlang.org" 708 | source: hosted 709 | version: "3.5.0" 710 | yaml: 711 | dependency: transitive 712 | description: 713 | name: yaml 714 | url: "https://pub.dartlang.org" 715 | source: hosted 716 | version: "2.2.0" 717 | sdks: 718 | dart: ">=2.7.0 <3.0.0" 719 | flutter: ">=1.12.13+hotfix.5 <2.0.0" 720 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: todo_app 2 | description: A simple TODO app just for learning Flutter and firebase. 3 | version: 1.0.0+1 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | intl: 13 | flutter_local_notifications: ^1.2.1 14 | auto_route: ^0.3.1 15 | flushbar: ^1.9.1 16 | ff_navigation_bar: ^0.1.4 17 | flutter_flexible_toast: ^0.1.4 18 | cloud_firestore: ^0.13.4+2 19 | font_awesome_flutter: ^8.7.0 20 | flutter_svg: ^0.17.1 21 | uuid: ^2.0.4 22 | localstorage: ^3.0.0 23 | validators: ^2.0.0 24 | flutter_spinkit: ^4.1.2 25 | cupertino_icons: ^0.1.2 26 | equatable: ^1.1.0 27 | flutter_bloc: ^3.2.0 28 | simple_animations: ^1.3.8 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | build_runner: 35 | auto_route_generator: ^0.3.2 36 | 37 | flutter: 38 | uses-material-design: true 39 | 40 | assets: 41 | - assets/images/ 42 | 43 | fonts: 44 | - family: Schyler 45 | fonts: 46 | - asset: fonts/OpenSans-Bold.ttf 47 | - asset: fonts/OpenSans-ExtraBold.ttf 48 | - asset: fonts/OpenSans-Light.ttf 49 | - asset: fonts/OpenSans-Regular.ttf 50 | - asset: fonts/OpenSans-SemiBold.ttf 51 | -------------------------------------------------------------------------------- /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 that Flutter provides. 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 | 11 | import 'package:todo_app/main.dart'; 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(MyApp()); 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 | --------------------------------------------------------------------------------