├── .flutter-plugins
├── .gitignore
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── me
│ │ │ └── tuannguyen
│ │ │ └── fluttertodo
│ │ │ └── MainActivity.java
│ │ └── res
│ │ ├── drawable
│ │ └── launch_background.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── assets
└── todo_list.svg
├── dark_editor.png
├── dark_list.png
├── editor.png
├── flutter_todo.iml
├── flutter_todo_android.iml
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── flutter_export_environment.sh
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── 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
│ └── main.m
├── lib
├── .env.example.dart
├── main.dart
├── models
│ ├── Priority.dart
│ ├── Todo.dart
│ ├── filter.dart
│ ├── settings.dart
│ └── user.dart
├── pages
│ ├── auth
│ │ └── auth_page.dart
│ ├── register
│ │ └── register_page.dart
│ ├── settings
│ │ └── settings_page.dart
│ └── todo
│ │ ├── todo_editor_page.dart
│ │ └── todo_list_page.dart
├── scoped_models
│ ├── app_model.dart
│ └── connected_model.dart
└── widgets
│ ├── form_fields
│ ├── priority_form_field.dart
│ └── toggle_form_field.dart
│ ├── helpers
│ ├── confirm_dialog.dart
│ ├── message_dialog.dart
│ └── priority_helper.dart
│ ├── todo
│ ├── shortcuts_enabled_todo_fab.dart
│ ├── todo_card.dart
│ └── todo_list_view.dart
│ └── ui_elements
│ ├── loading_modal.dart
│ └── rounded_button.dart
├── list.png
└── pubspec.yaml
/.flutter-plugins:
--------------------------------------------------------------------------------
1 | shared_preferences=/Volumes/Storage/Developer/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.3+4/
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | .pub/
7 | build/
8 | # If you're building an application, you may want to check-in your pubspec.lock
9 | pubspec.lock
10 |
11 | # Directory created by dartdoc
12 | # If you don't generate documentation locally you can remove this line.
13 | doc/api/
14 |
15 | .DS_Store
16 | .idea/
17 | .env.dart
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Flutter",
9 | "request": "launch",
10 | "type": "dart"
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter Todo
2 |
3 | Yet another Todo app, now using Flutter.
4 |
5 | ## Getting Started
6 |
7 | This Todo app is implemented using Flutter (with Scoped Model for state management) and Firebase.
8 |
9 | Features:
10 |
11 | - Create/edit todo
12 | - Delete todo by swipping
13 | - Mark done/not done in list
14 | - Filter todo list by status (all/done/not done)
15 | - Change theme (light to dark and vice versa) at runtime
16 | - Enable shortcuts to create todo
17 | - Login/logout
18 | - Register new account
19 |
20 | 
21 | 
22 | 
23 | 
24 |
25 | To get start, run below command in Terminal
26 |
27 | ```bash
28 | cp .env.example.dart .env.dart
29 | ```
30 |
31 | then add Firebase database's URL and API key to .env.dart.
32 |
33 | ---
34 |
35 | For more information about Flutter, visit [Flutter web site](https://flutter.io/).
36 | For more information about Firebase, visit [Firebase web site](https://firebase.google.com/).
37 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.class
3 | .gradle
4 | /local.properties
5 | /.idea/workspace.xml
6 | /.idea/libraries
7 | .DS_Store
8 | /build
9 | /captures
10 | GeneratedPluginRegistrant.java
11 |
--------------------------------------------------------------------------------
/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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
26 |
27 | android {
28 | compileSdkVersion 27
29 |
30 | lintOptions {
31 | disable 'InvalidPackage'
32 | }
33 |
34 | defaultConfig {
35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 | applicationId "me.tuannguyen.fluttertodo"
37 | minSdkVersion 16
38 | targetSdkVersion 27
39 | versionCode flutterVersionCode.toInteger()
40 | versionName flutterVersionName
41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
42 | }
43 |
44 | buildTypes {
45 | release {
46 | // TODO: Add your own signing config for the release build.
47 | // Signing with the debug keys for now, so `flutter run --release` works.
48 | signingConfig signingConfigs.debug
49 | }
50 | }
51 | }
52 |
53 | flutter {
54 | source '../..'
55 | }
56 |
57 | dependencies {
58 | testImplementation 'junit:junit:4.12'
59 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
61 | }
62 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/android/app/src/main/java/me/tuannguyen/fluttertodo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package me.tuannguyen.fluttertodo;
2 |
3 | import android.os.Bundle;
4 | import io.flutter.app.FlutterActivity;
5 | import io.flutter.plugins.GeneratedPluginRegistrant;
6 |
7 | public class MainActivity extends FlutterActivity {
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | GeneratedPluginRegistrant.registerWith(this);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.2.0'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Sep 26 21:25:02 ICT 2018
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.6-all.zip
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/todo_list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dark_editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/dark_editor.png
--------------------------------------------------------------------------------
/dark_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/dark_list.png
--------------------------------------------------------------------------------
/editor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/editor.png
--------------------------------------------------------------------------------
/flutter_todo.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/flutter_todo_android.iml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/app.flx
37 | /Flutter/app.zip
38 | /Flutter/flutter_assets/
39 | /Flutter/App.framework
40 | /Flutter/Flutter.framework
41 | /Flutter/Generated.xcconfig
42 | /ServiceDefinitions.json
43 |
44 | Pods/
45 | .symlinks/
46 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 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=/Volumes/Storage/Developer/flutter"
4 | export "FLUTTER_APPLICATION_PATH=/Volumes/Storage/Developer/projects/flutter/flutter_todo"
5 | export "FLUTTER_TARGET=/Volumes/Storage/Developer/projects/flutter/flutter_todo/lib/main.dart"
6 | export "FLUTTER_BUILD_DIR=build"
7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios"
8 | export "FLUTTER_FRAMEWORK_DIR=/Volumes/Storage/Developer/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 | def parse_KV_file(file, separator='=')
8 | file_abs_path = File.expand_path(file)
9 | if !File.exists? file_abs_path
10 | return [];
11 | end
12 | pods_ary = []
13 | skip_line_start_symbols = ["#", "/"]
14 | File.foreach(file_abs_path) { |line|
15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
16 | plugin = line.split(pattern=separator)
17 | if plugin.length == 2
18 | podname = plugin[0].strip()
19 | path = plugin[1].strip()
20 | podpath = File.expand_path("#{path}", file_abs_path)
21 | pods_ary.push({:name => podname, :path => podpath});
22 | else
23 | puts "Invalid plugin specification: #{line}"
24 | end
25 | }
26 | return pods_ary
27 | end
28 |
29 | target 'Runner' do
30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
31 | # referring to absolute paths on developers' machines.
32 | system('rm -rf .symlinks')
33 | system('mkdir -p .symlinks/plugins')
34 |
35 | # Flutter Pods
36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
37 | if generated_xcode_build_settings.empty?
38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
39 | end
40 | generated_xcode_build_settings.map { |p|
41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
42 | symlink = File.join('.symlinks', 'flutter')
43 | File.symlink(File.dirname(p[:path]), symlink)
44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
45 | end
46 | }
47 |
48 | # Plugin Pods
49 | plugin_pods = parse_KV_file('../.flutter-plugins')
50 | plugin_pods.map { |p|
51 | symlink = File.join('.symlinks', 'plugins', p[:name])
52 | File.symlink(p[:path], symlink)
53 | pod p[:name], :path => File.join(symlink, 'ios')
54 | }
55 | end
56 |
57 | post_install do |installer|
58 | installer.pods_project.targets.each do |target|
59 | target.build_configurations.each do |config|
60 | config.build_settings['ENABLE_BITCODE'] = 'NO'
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - shared_preferences (0.0.1):
4 | - Flutter
5 |
6 | DEPENDENCIES:
7 | - Flutter (from `.symlinks/flutter/ios`)
8 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
9 |
10 | EXTERNAL SOURCES:
11 | Flutter:
12 | :path: ".symlinks/flutter/ios"
13 | shared_preferences:
14 | :path: ".symlinks/plugins/shared_preferences/ios"
15 |
16 | SPEC CHECKSUMS:
17 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
18 | shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523
19 |
20 | PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2
21 |
22 | COCOAPODS: 1.7.1
23 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0A5641A7812B87519C05ECC1 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DB0C7DF3620D65935B7E58F /* libPods-Runner.a */; };
11 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
18 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
19 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
20 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
21 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
22 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXCopyFilesBuildPhase section */
26 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
27 | isa = PBXCopyFilesBuildPhase;
28 | buildActionMask = 2147483647;
29 | dstPath = "";
30 | dstSubfolderSpec = 10;
31 | files = (
32 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
33 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
34 | );
35 | name = "Embed Frameworks";
36 | runOnlyForDeploymentPostprocessing = 0;
37 | };
38 | /* End PBXCopyFilesBuildPhase section */
39 |
40 | /* Begin PBXFileReference section */
41 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
42 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
43 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
44 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
45 | 3DB0C7DF3620D65935B7E58F /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
46 | 754F179BFD22182A355F4A51 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
47 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
48 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
49 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
50 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
51 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
52 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
54 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
55 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
56 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
57 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
58 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
59 | 993FE8798829E0B99204436E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
60 | /* End PBXFileReference section */
61 |
62 | /* Begin PBXFrameworksBuildPhase section */
63 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
64 | isa = PBXFrameworksBuildPhase;
65 | buildActionMask = 2147483647;
66 | files = (
67 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
68 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
69 | 0A5641A7812B87519C05ECC1 /* libPods-Runner.a in Frameworks */,
70 | );
71 | runOnlyForDeploymentPostprocessing = 0;
72 | };
73 | /* End PBXFrameworksBuildPhase section */
74 |
75 | /* Begin PBXGroup section */
76 | 1D06AA122AABD4D2E01E5726 /* Frameworks */ = {
77 | isa = PBXGroup;
78 | children = (
79 | 3DB0C7DF3620D65935B7E58F /* libPods-Runner.a */,
80 | );
81 | name = Frameworks;
82 | sourceTree = "";
83 | };
84 | 9740EEB11CF90186004384FC /* Flutter */ = {
85 | isa = PBXGroup;
86 | children = (
87 | 3B80C3931E831B6300D905FE /* App.framework */,
88 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
89 | 9740EEBA1CF902C7004384FC /* Flutter.framework */,
90 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
91 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
92 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
93 | );
94 | name = Flutter;
95 | sourceTree = "";
96 | };
97 | 97C146E51CF9000F007C117D = {
98 | isa = PBXGroup;
99 | children = (
100 | 9740EEB11CF90186004384FC /* Flutter */,
101 | 97C146F01CF9000F007C117D /* Runner */,
102 | 97C146EF1CF9000F007C117D /* Products */,
103 | 9C7135DC87A1E34F4B5A5400 /* Pods */,
104 | 1D06AA122AABD4D2E01E5726 /* Frameworks */,
105 | );
106 | sourceTree = "";
107 | };
108 | 97C146EF1CF9000F007C117D /* Products */ = {
109 | isa = PBXGroup;
110 | children = (
111 | 97C146EE1CF9000F007C117D /* Runner.app */,
112 | );
113 | name = Products;
114 | sourceTree = "";
115 | };
116 | 97C146F01CF9000F007C117D /* Runner */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
120 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
121 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
122 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
123 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
124 | 97C147021CF9000F007C117D /* Info.plist */,
125 | 97C146F11CF9000F007C117D /* Supporting Files */,
126 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
127 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
128 | );
129 | path = Runner;
130 | sourceTree = "";
131 | };
132 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
133 | isa = PBXGroup;
134 | children = (
135 | 97C146F21CF9000F007C117D /* main.m */,
136 | );
137 | name = "Supporting Files";
138 | sourceTree = "";
139 | };
140 | 9C7135DC87A1E34F4B5A5400 /* Pods */ = {
141 | isa = PBXGroup;
142 | children = (
143 | 754F179BFD22182A355F4A51 /* Pods-Runner.debug.xcconfig */,
144 | 993FE8798829E0B99204436E /* Pods-Runner.release.xcconfig */,
145 | );
146 | name = Pods;
147 | sourceTree = "";
148 | };
149 | /* End PBXGroup section */
150 |
151 | /* Begin PBXNativeTarget section */
152 | 97C146ED1CF9000F007C117D /* Runner */ = {
153 | isa = PBXNativeTarget;
154 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
155 | buildPhases = (
156 | 8845F48388604A94023546F5 /* [CP] Check Pods Manifest.lock */,
157 | 9740EEB61CF901F6004384FC /* Run Script */,
158 | 97C146EA1CF9000F007C117D /* Sources */,
159 | 97C146EB1CF9000F007C117D /* Frameworks */,
160 | 97C146EC1CF9000F007C117D /* Resources */,
161 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
162 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
163 | 96BFAEA7DAC23388AC3603CB /* [CP] Embed Pods Frameworks */,
164 | );
165 | buildRules = (
166 | );
167 | dependencies = (
168 | );
169 | name = Runner;
170 | productName = Runner;
171 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
172 | productType = "com.apple.product-type.application";
173 | };
174 | /* End PBXNativeTarget section */
175 |
176 | /* Begin PBXProject section */
177 | 97C146E61CF9000F007C117D /* Project object */ = {
178 | isa = PBXProject;
179 | attributes = {
180 | LastUpgradeCheck = 0910;
181 | ORGANIZATIONNAME = "The Chromium Authors";
182 | TargetAttributes = {
183 | 97C146ED1CF9000F007C117D = {
184 | CreatedOnToolsVersion = 7.3.1;
185 | };
186 | };
187 | };
188 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
189 | compatibilityVersion = "Xcode 3.2";
190 | developmentRegion = English;
191 | hasScannedForEncodings = 0;
192 | knownRegions = (
193 | en,
194 | Base,
195 | );
196 | mainGroup = 97C146E51CF9000F007C117D;
197 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
198 | projectDirPath = "";
199 | projectRoot = "";
200 | targets = (
201 | 97C146ED1CF9000F007C117D /* Runner */,
202 | );
203 | };
204 | /* End PBXProject section */
205 |
206 | /* Begin PBXResourcesBuildPhase section */
207 | 97C146EC1CF9000F007C117D /* Resources */ = {
208 | isa = PBXResourcesBuildPhase;
209 | buildActionMask = 2147483647;
210 | files = (
211 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
212 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
213 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
214 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
215 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
216 | );
217 | runOnlyForDeploymentPostprocessing = 0;
218 | };
219 | /* End PBXResourcesBuildPhase section */
220 |
221 | /* Begin PBXShellScriptBuildPhase section */
222 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
223 | isa = PBXShellScriptBuildPhase;
224 | buildActionMask = 2147483647;
225 | files = (
226 | );
227 | inputPaths = (
228 | );
229 | name = "Thin Binary";
230 | outputPaths = (
231 | );
232 | runOnlyForDeploymentPostprocessing = 0;
233 | shellPath = /bin/sh;
234 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
235 | };
236 | 8845F48388604A94023546F5 /* [CP] Check Pods Manifest.lock */ = {
237 | isa = PBXShellScriptBuildPhase;
238 | buildActionMask = 2147483647;
239 | files = (
240 | );
241 | inputPaths = (
242 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
243 | "${PODS_ROOT}/Manifest.lock",
244 | );
245 | name = "[CP] Check Pods Manifest.lock";
246 | outputPaths = (
247 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | shellPath = /bin/sh;
251 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
252 | showEnvVarsInLog = 0;
253 | };
254 | 96BFAEA7DAC23388AC3603CB /* [CP] Embed Pods Frameworks */ = {
255 | isa = PBXShellScriptBuildPhase;
256 | buildActionMask = 2147483647;
257 | files = (
258 | );
259 | inputPaths = (
260 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
261 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
262 | );
263 | name = "[CP] Embed Pods Frameworks";
264 | outputPaths = (
265 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
266 | );
267 | runOnlyForDeploymentPostprocessing = 0;
268 | shellPath = /bin/sh;
269 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
270 | showEnvVarsInLog = 0;
271 | };
272 | 9740EEB61CF901F6004384FC /* Run Script */ = {
273 | isa = PBXShellScriptBuildPhase;
274 | buildActionMask = 2147483647;
275 | files = (
276 | );
277 | inputPaths = (
278 | );
279 | name = "Run Script";
280 | outputPaths = (
281 | );
282 | runOnlyForDeploymentPostprocessing = 0;
283 | shellPath = /bin/sh;
284 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
285 | };
286 | /* End PBXShellScriptBuildPhase section */
287 |
288 | /* Begin PBXSourcesBuildPhase section */
289 | 97C146EA1CF9000F007C117D /* Sources */ = {
290 | isa = PBXSourcesBuildPhase;
291 | buildActionMask = 2147483647;
292 | files = (
293 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
294 | 97C146F31CF9000F007C117D /* main.m in Sources */,
295 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
296 | );
297 | runOnlyForDeploymentPostprocessing = 0;
298 | };
299 | /* End PBXSourcesBuildPhase section */
300 |
301 | /* Begin PBXVariantGroup section */
302 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
303 | isa = PBXVariantGroup;
304 | children = (
305 | 97C146FB1CF9000F007C117D /* Base */,
306 | );
307 | name = Main.storyboard;
308 | sourceTree = "";
309 | };
310 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
311 | isa = PBXVariantGroup;
312 | children = (
313 | 97C147001CF9000F007C117D /* Base */,
314 | );
315 | name = LaunchScreen.storyboard;
316 | sourceTree = "";
317 | };
318 | /* End PBXVariantGroup section */
319 |
320 | /* Begin XCBuildConfiguration section */
321 | 97C147031CF9000F007C117D /* Debug */ = {
322 | isa = XCBuildConfiguration;
323 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
324 | buildSettings = {
325 | ALWAYS_SEARCH_USER_PATHS = NO;
326 | CLANG_ANALYZER_NONNULL = YES;
327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
328 | CLANG_CXX_LIBRARY = "libc++";
329 | CLANG_ENABLE_MODULES = YES;
330 | CLANG_ENABLE_OBJC_ARC = YES;
331 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
332 | CLANG_WARN_BOOL_CONVERSION = YES;
333 | CLANG_WARN_COMMA = YES;
334 | CLANG_WARN_CONSTANT_CONVERSION = YES;
335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
336 | CLANG_WARN_EMPTY_BODY = YES;
337 | CLANG_WARN_ENUM_CONVERSION = YES;
338 | CLANG_WARN_INFINITE_RECURSION = YES;
339 | CLANG_WARN_INT_CONVERSION = YES;
340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
344 | CLANG_WARN_STRICT_PROTOTYPES = YES;
345 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
346 | CLANG_WARN_UNREACHABLE_CODE = YES;
347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
349 | COPY_PHASE_STRIP = NO;
350 | DEBUG_INFORMATION_FORMAT = dwarf;
351 | ENABLE_STRICT_OBJC_MSGSEND = YES;
352 | ENABLE_TESTABILITY = YES;
353 | GCC_C_LANGUAGE_STANDARD = gnu99;
354 | GCC_DYNAMIC_NO_PIC = NO;
355 | GCC_NO_COMMON_BLOCKS = YES;
356 | GCC_OPTIMIZATION_LEVEL = 0;
357 | GCC_PREPROCESSOR_DEFINITIONS = (
358 | "DEBUG=1",
359 | "$(inherited)",
360 | );
361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
363 | GCC_WARN_UNDECLARED_SELECTOR = YES;
364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
365 | GCC_WARN_UNUSED_FUNCTION = YES;
366 | GCC_WARN_UNUSED_VARIABLE = YES;
367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
368 | MTL_ENABLE_DEBUG_INFO = YES;
369 | ONLY_ACTIVE_ARCH = YES;
370 | SDKROOT = iphoneos;
371 | TARGETED_DEVICE_FAMILY = "1,2";
372 | };
373 | name = Debug;
374 | };
375 | 97C147041CF9000F007C117D /* Release */ = {
376 | isa = XCBuildConfiguration;
377 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
378 | buildSettings = {
379 | ALWAYS_SEARCH_USER_PATHS = NO;
380 | CLANG_ANALYZER_NONNULL = YES;
381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
382 | CLANG_CXX_LIBRARY = "libc++";
383 | CLANG_ENABLE_MODULES = YES;
384 | CLANG_ENABLE_OBJC_ARC = YES;
385 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
386 | CLANG_WARN_BOOL_CONVERSION = YES;
387 | CLANG_WARN_COMMA = YES;
388 | CLANG_WARN_CONSTANT_CONVERSION = YES;
389 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
390 | CLANG_WARN_EMPTY_BODY = YES;
391 | CLANG_WARN_ENUM_CONVERSION = YES;
392 | CLANG_WARN_INFINITE_RECURSION = YES;
393 | CLANG_WARN_INT_CONVERSION = YES;
394 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
398 | CLANG_WARN_STRICT_PROTOTYPES = YES;
399 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
400 | CLANG_WARN_UNREACHABLE_CODE = YES;
401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
402 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
403 | COPY_PHASE_STRIP = NO;
404 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
405 | ENABLE_NS_ASSERTIONS = NO;
406 | ENABLE_STRICT_OBJC_MSGSEND = YES;
407 | GCC_C_LANGUAGE_STANDARD = gnu99;
408 | GCC_NO_COMMON_BLOCKS = YES;
409 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
410 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
411 | GCC_WARN_UNDECLARED_SELECTOR = YES;
412 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
413 | GCC_WARN_UNUSED_FUNCTION = YES;
414 | GCC_WARN_UNUSED_VARIABLE = YES;
415 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
416 | MTL_ENABLE_DEBUG_INFO = NO;
417 | SDKROOT = iphoneos;
418 | TARGETED_DEVICE_FAMILY = "1,2";
419 | VALIDATE_PRODUCT = YES;
420 | };
421 | name = Release;
422 | };
423 | 97C147061CF9000F007C117D /* Debug */ = {
424 | isa = XCBuildConfiguration;
425 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
426 | buildSettings = {
427 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
428 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
429 | ENABLE_BITCODE = NO;
430 | FRAMEWORK_SEARCH_PATHS = (
431 | "$(inherited)",
432 | "$(PROJECT_DIR)/Flutter",
433 | );
434 | INFOPLIST_FILE = Runner/Info.plist;
435 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
436 | LIBRARY_SEARCH_PATHS = (
437 | "$(inherited)",
438 | "$(PROJECT_DIR)/Flutter",
439 | );
440 | PRODUCT_BUNDLE_IDENTIFIER = me.tuannguyen.flutterTodo;
441 | PRODUCT_NAME = "$(TARGET_NAME)";
442 | VERSIONING_SYSTEM = "apple-generic";
443 | };
444 | name = Debug;
445 | };
446 | 97C147071CF9000F007C117D /* Release */ = {
447 | isa = XCBuildConfiguration;
448 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
449 | buildSettings = {
450 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
451 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
452 | ENABLE_BITCODE = NO;
453 | FRAMEWORK_SEARCH_PATHS = (
454 | "$(inherited)",
455 | "$(PROJECT_DIR)/Flutter",
456 | );
457 | INFOPLIST_FILE = Runner/Info.plist;
458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
459 | LIBRARY_SEARCH_PATHS = (
460 | "$(inherited)",
461 | "$(PROJECT_DIR)/Flutter",
462 | );
463 | PRODUCT_BUNDLE_IDENTIFIER = me.tuannguyen.flutterTodo;
464 | PRODUCT_NAME = "$(TARGET_NAME)";
465 | VERSIONING_SYSTEM = "apple-generic";
466 | };
467 | name = Release;
468 | };
469 | /* End XCBuildConfiguration section */
470 |
471 | /* Begin XCConfigurationList section */
472 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
473 | isa = XCConfigurationList;
474 | buildConfigurations = (
475 | 97C147031CF9000F007C117D /* Debug */,
476 | 97C147041CF9000F007C117D /* Release */,
477 | );
478 | defaultConfigurationIsVisible = 0;
479 | defaultConfigurationName = Release;
480 | };
481 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
482 | isa = XCConfigurationList;
483 | buildConfigurations = (
484 | 97C147061CF9000F007C117D /* Debug */,
485 | 97C147071CF9000F007C117D /* Release */,
486 | );
487 | defaultConfigurationIsVisible = 0;
488 | defaultConfigurationName = Release;
489 | };
490 | /* End XCConfigurationList section */
491 | };
492 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
493 | }
494 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/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.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildSystemType
6 | Original
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuannguyendotme/flutter_todo/6261e20aab693c58e5c018629adb3fa84288a22e/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 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | Flutter Todo
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/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/.env.example.dart:
--------------------------------------------------------------------------------
1 | class Configure {
2 | static const String AppName = 'Flutter Todo';
3 | static const String FirebaseUrl = '[YOUR FIREBASE URL]';
4 | static const String ApiKey = '[YOUR API KEY]';
5 | }
6 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:scoped_model/scoped_model.dart';
4 |
5 | import 'package:flutter_todo/.env.dart';
6 | import 'package:flutter_todo/scoped_models/app_model.dart';
7 | import 'package:flutter_todo/pages/register/register_page.dart';
8 | import 'package:flutter_todo/pages/settings/settings_page.dart';
9 | import 'package:flutter_todo/pages/auth/auth_page.dart';
10 | import 'package:flutter_todo/pages/todo/todo_editor_page.dart';
11 | import 'package:flutter_todo/pages/todo/todo_list_page.dart';
12 |
13 | void main() async {
14 | runApp(TodoApp());
15 | }
16 |
17 | class TodoApp extends StatefulWidget {
18 | @override
19 | State createState() {
20 | return _TodoAppState();
21 | }
22 | }
23 |
24 | class _TodoAppState extends State {
25 | AppModel _model;
26 | bool _isAuthenticated = false;
27 | bool _isDarkThemeUsed = false;
28 |
29 | @override
30 | void initState() {
31 | _model = AppModel();
32 |
33 | _model.loadSettings();
34 | _model.autoAuthentication();
35 |
36 | _model.userSubject.listen((bool isAuthenticated) {
37 | setState(() {
38 | _isAuthenticated = isAuthenticated;
39 | });
40 | });
41 |
42 | _model.themeSubject.listen((bool isDarkThemeUsed) {
43 | setState(() {
44 | _isDarkThemeUsed = isDarkThemeUsed;
45 | });
46 | });
47 |
48 | super.initState();
49 | }
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | return ScopedModel(
54 | model: _model,
55 | child: MaterialApp(
56 | title: Configure.AppName,
57 | debugShowCheckedModeBanner: false,
58 | theme: ThemeData(
59 | accentColor: Colors.blue,
60 | brightness: _isDarkThemeUsed ? Brightness.dark : Brightness.light,
61 | ),
62 | routes: {
63 | '/': (BuildContext context) =>
64 | _isAuthenticated ? TodoListPage(_model) : AuthPage(),
65 | '/editor': (BuildContext context) =>
66 | _isAuthenticated ? TodoEditorPage() : AuthPage(),
67 | '/register': (BuildContext context) =>
68 | _isAuthenticated ? TodoListPage(_model) : RegisterPage(),
69 | '/settings': (BuildContext context) =>
70 | _isAuthenticated ? SettingsPage(_model) : AuthPage(),
71 | },
72 | onUnknownRoute: (RouteSettings settings) {
73 | return MaterialPageRoute(
74 | builder: (BuildContext context) =>
75 | _isAuthenticated ? TodoListPage(_model) : AuthPage(),
76 | );
77 | },
78 | ),
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/models/Priority.dart:
--------------------------------------------------------------------------------
1 | enum Priority {
2 | High,
3 | Medium,
4 | Low,
5 | }
6 |
--------------------------------------------------------------------------------
/lib/models/Todo.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:flutter_todo/models/priority.dart';
4 |
5 | class Todo {
6 | final String id;
7 | final String title;
8 | final String content;
9 | final Priority priority;
10 | final bool isDone;
11 | final String userId;
12 |
13 | Todo({
14 | @required this.id,
15 | @required this.title,
16 | this.content,
17 | this.priority = Priority.Low,
18 | this.isDone = false,
19 | @required this.userId,
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/lib/models/filter.dart:
--------------------------------------------------------------------------------
1 | enum Filter {
2 | All,
3 | Done,
4 | NotDone,
5 | }
6 |
--------------------------------------------------------------------------------
/lib/models/settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Settings {
4 | final bool isShortcutsEnabled;
5 | final bool isDarkThemeUsed;
6 |
7 | Settings({
8 | @required this.isShortcutsEnabled,
9 | @required this.isDarkThemeUsed,
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/lib/models/user.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class User {
4 | final String id;
5 | final String email;
6 | final String token;
7 |
8 | User({
9 | @required this.id,
10 | @required this.email,
11 | @required this.token,
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/lib/pages/auth/auth_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:scoped_model/scoped_model.dart';
4 |
5 | import 'package:flutter_todo/.env.dart';
6 | import 'package:flutter_todo/widgets/ui_elements/loading_modal.dart';
7 | import 'package:flutter_todo/widgets/helpers/message_dialog.dart';
8 | import 'package:flutter_todo/scoped_models/app_model.dart';
9 | import 'package:flutter_todo/widgets/ui_elements/rounded_button.dart';
10 |
11 | class AuthPage extends StatefulWidget {
12 | @override
13 | State createState() {
14 | return _AuthPageState();
15 | }
16 | }
17 |
18 | class _AuthPageState extends State {
19 | final Map _formData = {
20 | 'email': null,
21 | 'password': null,
22 | };
23 |
24 | final GlobalKey _formKey = GlobalKey();
25 |
26 | void _authenticate(AppModel model) async {
27 | if (!_formKey.currentState.validate()) {
28 | return;
29 | }
30 |
31 | _formKey.currentState.save();
32 |
33 | Map authResult =
34 | await model.authenticate(_formData['email'], _formData['password']);
35 |
36 | if (authResult['success']) {
37 | } else {
38 | MessageDialog.show(context, message: authResult['message']);
39 | }
40 | }
41 |
42 | Widget _buildEmailField() {
43 | return TextFormField(
44 | decoration: InputDecoration(labelText: 'Email'),
45 | validator: (value) {
46 | if (value.isEmpty ||
47 | !RegExp(r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
48 | .hasMatch(value)) {
49 | return 'Please enter a valid email';
50 | }
51 |
52 | return null;
53 | },
54 | onSaved: (value) {
55 | _formData['email'] = value;
56 | },
57 | );
58 | }
59 |
60 | Widget _buildPasswordField() {
61 | return TextFormField(
62 | obscureText: true,
63 | decoration: InputDecoration(labelText: 'Password'),
64 | validator: (value) {
65 | if (value.isEmpty) {
66 | return 'Please enter password';
67 | }
68 |
69 | return null;
70 | },
71 | onSaved: (value) {
72 | _formData['password'] = value;
73 | },
74 | );
75 | }
76 |
77 | Widget _buildButtonRow(AppModel model) {
78 | return Row(
79 | mainAxisAlignment: MainAxisAlignment.center,
80 | children: [
81 | RoundedButton(
82 | icon: Icon(Icons.edit),
83 | label: 'Register',
84 | onPressed: () {
85 | Navigator.pushNamed(context, '/register');
86 | },
87 | ),
88 | SizedBox(
89 | width: 20.0,
90 | ),
91 | RoundedButton(
92 | icon: Icon(Icons.lock_open),
93 | label: 'Login',
94 | onPressed: () => _authenticate(model),
95 | ),
96 | ],
97 | );
98 | }
99 |
100 | Widget _buildPageContent(AppModel model) {
101 | final double deviceWidth = MediaQuery.of(context).size.width;
102 | final double targetWidth = deviceWidth > 550 ? 500 : deviceWidth * 0.85;
103 |
104 | return Scaffold(
105 | appBar: AppBar(
106 | title: Text(Configure.AppName),
107 | backgroundColor: Colors.blue,
108 | ),
109 | body: Container(
110 | padding: EdgeInsets.all(10.0),
111 | child: Center(
112 | child: SingleChildScrollView(
113 | child: Container(
114 | width: targetWidth,
115 | child: Form(
116 | key: _formKey,
117 | child: Column(
118 | children: [
119 | _buildEmailField(),
120 | _buildPasswordField(),
121 | SizedBox(
122 | height: 20.0,
123 | ),
124 | _buildButtonRow(model),
125 | ],
126 | ),
127 | ),
128 | ),
129 | ),
130 | ),
131 | ),
132 | );
133 | }
134 |
135 | @override
136 | Widget build(BuildContext context) {
137 | return ScopedModelDescendant(
138 | builder: (BuildContext context, Widget child, AppModel model) {
139 | Stack mainStack = Stack(
140 | children: [
141 | _buildPageContent(model),
142 | ],
143 | );
144 |
145 | if (model.isLoading) {
146 | mainStack.children.add(LoadingModal());
147 | }
148 |
149 | return mainStack;
150 | },
151 | );
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/lib/pages/register/register_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:scoped_model/scoped_model.dart';
4 |
5 | import 'package:flutter_todo/.env.dart';
6 | import 'package:flutter_todo/widgets/ui_elements/loading_modal.dart';
7 | import 'package:flutter_todo/widgets/helpers/message_dialog.dart';
8 | import 'package:flutter_todo/scoped_models/app_model.dart';
9 | import 'package:flutter_todo/widgets/ui_elements/rounded_button.dart';
10 |
11 | class RegisterPage extends StatefulWidget {
12 | @override
13 | State createState() {
14 | return _RegisterPageState();
15 | }
16 | }
17 |
18 | class _RegisterPageState extends State {
19 | final Map _formData = {
20 | 'email': null,
21 | 'password': null,
22 | };
23 |
24 | final GlobalKey _formKey = GlobalKey();
25 | final TextEditingController _passwordController = TextEditingController();
26 |
27 | void _register(AppModel model) async {
28 | if (!_formKey.currentState.validate()) {
29 | return;
30 | }
31 |
32 | _formKey.currentState.save();
33 |
34 | Map authResult =
35 | await model.register(_formData['email'], _formData['password']);
36 |
37 | if (authResult['success']) {
38 | Navigator.pop(context);
39 | } else {
40 | MessageDialog.show(context, message: authResult['message']);
41 | }
42 | }
43 |
44 | Widget _buildEmailField() {
45 | return TextFormField(
46 | decoration: InputDecoration(labelText: 'Email'),
47 | validator: (value) {
48 | if (value.isEmpty ||
49 | !RegExp(r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
50 | .hasMatch(value)) {
51 | return 'Please enter a valid email';
52 | }
53 |
54 | return null;
55 | },
56 | onSaved: (value) {
57 | _formData['email'] = value;
58 | },
59 | );
60 | }
61 |
62 | Widget _buildConfirmPasswordField() {
63 | return TextFormField(
64 | obscureText: true,
65 | decoration: InputDecoration(labelText: 'Confirm Password'),
66 | validator: (value) {
67 | if (value != _passwordController.value.text) {
68 | return 'Password and confirm password are not match';
69 | }
70 |
71 | return null;
72 | },
73 | );
74 | }
75 |
76 | Widget _buildPasswordField() {
77 | return TextFormField(
78 | obscureText: true,
79 | decoration: InputDecoration(labelText: 'Password'),
80 | controller: _passwordController,
81 | validator: (value) {
82 | if (value.isEmpty || value.length < 6) {
83 | return 'Please enter valid password';
84 | }
85 |
86 | return null;
87 | },
88 | onSaved: (value) {
89 | _formData['password'] = value;
90 | },
91 | );
92 | }
93 |
94 | Widget _buildButtonRow(AppModel model) {
95 | return Row(
96 | mainAxisAlignment: MainAxisAlignment.center,
97 | children: [
98 | RoundedButton(
99 | icon: Icon(Icons.edit),
100 | label: 'Register',
101 | onPressed: () {
102 | _register(model);
103 | },
104 | ),
105 | ],
106 | );
107 | }
108 |
109 | Widget _buildPageContent(AppModel model) {
110 | final double deviceWidth = MediaQuery.of(context).size.width;
111 | final double targetWidth = deviceWidth > 550 ? 500 : deviceWidth * 0.85;
112 |
113 | return Scaffold(
114 | appBar: AppBar(
115 | title: Text(Configure.AppName),
116 | backgroundColor: Colors.blue,
117 | ),
118 | body: Container(
119 | padding: EdgeInsets.all(10.0),
120 | child: Center(
121 | child: SingleChildScrollView(
122 | child: Container(
123 | width: targetWidth,
124 | child: Form(
125 | key: _formKey,
126 | child: Column(
127 | children: [
128 | _buildEmailField(),
129 | _buildPasswordField(),
130 | _buildConfirmPasswordField(),
131 | SizedBox(
132 | height: 20.0,
133 | ),
134 | _buildButtonRow(model),
135 | ],
136 | ),
137 | ),
138 | ),
139 | ),
140 | ),
141 | ),
142 | );
143 | }
144 |
145 | @override
146 | Widget build(BuildContext context) {
147 | return ScopedModelDescendant(
148 | builder: (BuildContext context, Widget child, AppModel model) {
149 | Stack mainStack = Stack(
150 | children: [
151 | _buildPageContent(model),
152 | ],
153 | );
154 |
155 | if (model.isLoading) {
156 | mainStack.children.add(LoadingModal());
157 | }
158 |
159 | return mainStack;
160 | },
161 | );
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/lib/pages/settings/settings_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:scoped_model/scoped_model.dart';
4 |
5 | import 'package:flutter_todo/scoped_models/app_model.dart';
6 | import 'package:flutter_todo/widgets/ui_elements/loading_modal.dart';
7 | import 'package:flutter_todo/widgets/helpers/confirm_dialog.dart';
8 |
9 | class SettingsPage extends StatefulWidget {
10 | final AppModel model;
11 |
12 | SettingsPage(this.model);
13 |
14 | @override
15 | State createState() {
16 | return _SettingsPageState();
17 | }
18 | }
19 |
20 | class _SettingsPageState extends State {
21 | Widget _buildAppBar(BuildContext context, AppModel model) {
22 | return AppBar(
23 | title: Text('Settings'),
24 | backgroundColor: Colors.blue,
25 | actions: [
26 | IconButton(
27 | icon: Icon(Icons.lock),
28 | onPressed: () async {
29 | bool confirm = await ConfirmDialog.show(context);
30 |
31 | if (confirm) {
32 | Navigator.pop(context);
33 |
34 | model.logout();
35 | }
36 | },
37 | ),
38 | ],
39 | );
40 | }
41 |
42 | Widget _buildPageContent(AppModel model) {
43 | return model.isLoading
44 | ? LoadingModal()
45 | : Scaffold(
46 | appBar: _buildAppBar(context, model),
47 | body: ListView(
48 | children: [
49 | SwitchListTile(
50 | activeColor: Colors.blue,
51 | value: model.settings.isShortcutsEnabled,
52 | onChanged: (value) {
53 | model.toggleIsShortcutEnabled();
54 | },
55 | title: Text('Enable shortcuts'),
56 | ),
57 | SwitchListTile(
58 | activeColor: Colors.blue,
59 | value: model.settings.isDarkThemeUsed,
60 | onChanged: (value) {
61 | model.toggleIsDarkThemeUsed();
62 | },
63 | title: Text('Use dark theme'),
64 | )
65 | ],
66 | ),
67 | );
68 | }
69 |
70 | @override
71 | Widget build(BuildContext context) {
72 | return ScopedModelDescendant(
73 | builder: (BuildContext context, Widget child, AppModel model) {
74 | return _buildPageContent(model);
75 | },
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/pages/todo/todo_editor_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:scoped_model/scoped_model.dart';
4 |
5 | import 'package:flutter_todo/.env.dart';
6 | import 'package:flutter_todo/models/todo.dart';
7 | import 'package:flutter_todo/models/priority.dart';
8 | import 'package:flutter_todo/scoped_models/app_model.dart';
9 | import 'package:flutter_todo/widgets/helpers/message_dialog.dart';
10 | import 'package:flutter_todo/widgets/helpers/confirm_dialog.dart';
11 | import 'package:flutter_todo/widgets/ui_elements/loading_modal.dart';
12 | import 'package:flutter_todo/widgets/form_fields/priority_form_field.dart';
13 | import 'package:flutter_todo/widgets/form_fields/toggle_form_field.dart';
14 |
15 | class TodoEditorPage extends StatefulWidget {
16 | @override
17 | State createState() {
18 | return _TodoEditorPageState();
19 | }
20 | }
21 |
22 | class _TodoEditorPageState extends State {
23 | final Map _formData = {
24 | 'title': null,
25 | 'content': null,
26 | 'priority': Priority.Low,
27 | 'isDone': false
28 | };
29 | final GlobalKey _formKey = GlobalKey();
30 |
31 | Widget _buildAppBar(AppModel model) {
32 | return AppBar(
33 | title: Text(Configure.AppName),
34 | backgroundColor: Colors.blue,
35 | actions: [
36 | IconButton(
37 | icon: Icon(Icons.lock),
38 | onPressed: () async {
39 | bool confirm = await ConfirmDialog.show(context);
40 |
41 | if (confirm) {
42 | Navigator.pop(context);
43 |
44 | model.logout();
45 | }
46 | },
47 | ),
48 | ],
49 | );
50 | }
51 |
52 | Widget _buildFloatingActionButton(AppModel model) {
53 | return FloatingActionButton(
54 | child: Icon(Icons.save),
55 | onPressed: () {
56 | if (!_formKey.currentState.validate()) {
57 | return;
58 | }
59 |
60 | _formKey.currentState.save();
61 |
62 | if (model.currentTodo != null && model.currentTodo.id != null) {
63 | model
64 | .updateTodo(
65 | _formData['title'],
66 | _formData['content'],
67 | _formData['priority'],
68 | _formData['isDone'],
69 | )
70 | .then((bool success) {
71 | if (success) {
72 | model.setCurrentTodo(null);
73 |
74 | Navigator.pop(context);
75 | } else {
76 | MessageDialog.show(context);
77 | }
78 | });
79 | } else {
80 | model
81 | .createTodo(
82 | _formData['title'],
83 | _formData['content'],
84 | _formData['priority'],
85 | _formData['isDone'],
86 | )
87 | .then((bool success) {
88 | if (success) {
89 | Navigator.pop(context);
90 | } else {
91 | MessageDialog.show(context);
92 | }
93 | });
94 | }
95 | },
96 | );
97 | }
98 |
99 | Widget _buildTitleField(Todo todo) {
100 | return TextFormField(
101 | decoration: InputDecoration(labelText: 'Title'),
102 | initialValue: todo != null ? todo.title : '',
103 | validator: (value) {
104 | if (value.isEmpty) {
105 | return 'Please enter todo\'s title';
106 | }
107 |
108 | return null;
109 | },
110 | onSaved: (value) {
111 | _formData['title'] = value;
112 | },
113 | );
114 | }
115 |
116 | Widget _buildContentField(Todo todo) {
117 | return TextFormField(
118 | decoration: InputDecoration(labelText: 'Content'),
119 | initialValue: todo != null ? todo.content : '',
120 | maxLines: 5,
121 | onSaved: (value) {
122 | _formData['content'] = value;
123 | },
124 | );
125 | }
126 |
127 | Widget _buildOthers(Todo todo) {
128 | final bool isDone = todo != null && todo.isDone;
129 | final Priority priority = todo != null ? todo.priority : Priority.Low;
130 |
131 | return Row(
132 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
133 | children: [
134 | ToggleFormField(
135 | initialValue: isDone,
136 | onSaved: (bool value) {
137 | _formData['isDone'] = value;
138 | },
139 | ),
140 | PriorityFormField(
141 | initialValue: priority,
142 | onSaved: (Priority value) {
143 | _formData['priority'] = value;
144 | },
145 | ),
146 | ],
147 | );
148 | }
149 |
150 | Widget _buildForm(AppModel model) {
151 | Todo todo = model.currentTodo;
152 |
153 | _formData['title'] = todo != null ? todo.title : null;
154 | _formData['content'] = todo != null ? todo.content : null;
155 | _formData['priority'] = todo != null ? todo.priority : Priority.Low;
156 | _formData['isDone'] = todo != null ? todo.isDone : false;
157 |
158 | return Form(
159 | key: _formKey,
160 | child: ListView(
161 | children: [
162 | _buildTitleField(todo),
163 | _buildContentField(todo),
164 | SizedBox(
165 | height: 12.0,
166 | ),
167 | _buildOthers(todo),
168 | ],
169 | ),
170 | );
171 | }
172 |
173 | Widget _buildPageContent(AppModel model) {
174 | return Scaffold(
175 | appBar: _buildAppBar(model),
176 | floatingActionButton: _buildFloatingActionButton(model),
177 | body: Container(
178 | padding: EdgeInsets.all(10.0),
179 | child: Center(
180 | child: _buildForm(model),
181 | ),
182 | ),
183 | );
184 | }
185 |
186 | @override
187 | Widget build(BuildContext context) {
188 | return ScopedModelDescendant(
189 | builder: (BuildContext context, Widget child, AppModel model) {
190 | Stack mainStack = Stack(
191 | children: [
192 | _buildPageContent(model),
193 | ],
194 | );
195 |
196 | if (model.isLoading) {
197 | mainStack.children.add(LoadingModal());
198 | }
199 |
200 | return mainStack;
201 | },
202 | );
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/lib/pages/todo/todo_list_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:scoped_model/scoped_model.dart';
4 |
5 | import 'package:flutter_todo/.env.dart';
6 | import 'package:flutter_todo/models/filter.dart';
7 | import 'package:flutter_todo/scoped_models/app_model.dart';
8 | import 'package:flutter_todo/widgets/helpers/confirm_dialog.dart';
9 | import 'package:flutter_todo/widgets/ui_elements/loading_modal.dart';
10 | import 'package:flutter_todo/widgets/todo/todo_list_view.dart';
11 | import 'package:flutter_todo/widgets/todo/shortcuts_enabled_todo_fab.dart';
12 |
13 | class TodoListPage extends StatefulWidget {
14 | final AppModel model;
15 |
16 | TodoListPage(this.model);
17 |
18 | @override
19 | State createState() {
20 | return _TodoListPageState();
21 | }
22 | }
23 |
24 | class _TodoListPageState extends State {
25 | @override
26 | void initState() {
27 | widget.model.fetchTodos();
28 |
29 | super.initState();
30 | }
31 |
32 | Widget _buildAppBar(AppModel model) {
33 | return AppBar(
34 | title: Text(Configure.AppName),
35 | backgroundColor: Colors.blue,
36 | actions: [
37 | IconButton(
38 | icon: Icon(Icons.lock),
39 | onPressed: () async {
40 | bool confirm = await ConfirmDialog.show(context);
41 |
42 | if (confirm) {
43 | model.logout();
44 | }
45 | },
46 | ),
47 | PopupMenuButton(
48 | onSelected: (String choice) {
49 | switch (choice) {
50 | case 'Settings':
51 | Navigator.pushNamed(context, '/settings');
52 | }
53 | },
54 | itemBuilder: (BuildContext context) {
55 | return [
56 | PopupMenuItem(
57 | value: 'Settings',
58 | child: Text('Settings'),
59 | )
60 | ];
61 | },
62 | ),
63 | ],
64 | );
65 | }
66 |
67 | Widget _buildFloatingActionButton(AppModel model) {
68 | if (model.settings.isShortcutsEnabled) {
69 | return ShortcutsEnabledTodoFab(model);
70 | } else {
71 | return FloatingActionButton(
72 | child: Icon(Icons.add),
73 | onPressed: () {
74 | model.setCurrentTodo(null);
75 |
76 | Navigator.pushNamed(context, '/editor');
77 | },
78 | );
79 | }
80 | }
81 |
82 | Widget _buildAllFlatButton(AppModel model) {
83 | return FlatButton(
84 | child: Container(
85 | child: Column(
86 | mainAxisSize: MainAxisSize.min,
87 | children: [
88 | Icon(
89 | Icons.all_inclusive,
90 | color: model.filter == Filter.All ? Colors.white : Colors.black,
91 | ),
92 | Text(
93 | 'All',
94 | style: TextStyle(
95 | color: model.filter == Filter.All ? Colors.white : Colors.black,
96 | ),
97 | )
98 | ],
99 | ),
100 | ),
101 | onPressed: () {
102 | model.applyFilter(Filter.All);
103 | },
104 | );
105 | }
106 |
107 | Widget _buildDoneFlatButton(AppModel model) {
108 | return FlatButton(
109 | child: Container(
110 | child: Column(
111 | mainAxisSize: MainAxisSize.min,
112 | children: [
113 | Icon(
114 | Icons.check,
115 | color: model.filter == Filter.Done ? Colors.white : Colors.black,
116 | ),
117 | Text(
118 | 'Done',
119 | style: TextStyle(
120 | color:
121 | model.filter == Filter.Done ? Colors.white : Colors.black,
122 | ),
123 | )
124 | ],
125 | ),
126 | ),
127 | onPressed: () {
128 | model.applyFilter(Filter.Done);
129 | },
130 | );
131 | }
132 |
133 | Widget _buildNotDoneFlatButton(AppModel model) {
134 | return FlatButton(
135 | child: Container(
136 | child: Column(
137 | mainAxisSize: MainAxisSize.min,
138 | children: [
139 | Icon(
140 | Icons.check_box_outline_blank,
141 | color:
142 | model.filter == Filter.NotDone ? Colors.white : Colors.black,
143 | ),
144 | Text(
145 | 'Not Done',
146 | style: TextStyle(
147 | color: model.filter == Filter.NotDone
148 | ? Colors.white
149 | : Colors.black,
150 | ),
151 | )
152 | ],
153 | ),
154 | ),
155 | onPressed: () {
156 | model.applyFilter(Filter.NotDone);
157 | },
158 | );
159 | }
160 |
161 | Widget _buildBottomAppBar(AppModel model) {
162 | return BottomAppBar(
163 | child: Row(
164 | mainAxisSize: MainAxisSize.max,
165 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
166 | children: [
167 | // SizedBox(),
168 | _buildAllFlatButton(model),
169 | _buildDoneFlatButton(model),
170 | _buildNotDoneFlatButton(model),
171 | // SizedBox(
172 | // width: 80.0,
173 | // ),
174 | ],
175 | ),
176 | color: Colors.blue,
177 | shape: CircularNotchedRectangle(),
178 | );
179 | }
180 |
181 | Widget _buildPageContent(AppModel model) {
182 | return Scaffold(
183 | appBar: _buildAppBar(model),
184 | // floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
185 | floatingActionButton: _buildFloatingActionButton(model),
186 | bottomNavigationBar: _buildBottomAppBar(model),
187 | body: TodoListView(),
188 | );
189 | }
190 |
191 | @override
192 | Widget build(BuildContext context) {
193 | return ScopedModelDescendant(
194 | builder: (BuildContext context, Widget child, AppModel model) {
195 | Stack stack = Stack(
196 | children: [
197 | _buildPageContent(model),
198 | ],
199 | );
200 |
201 | if (model.isLoading) {
202 | stack.children.add(LoadingModal());
203 | }
204 |
205 | return stack;
206 | },
207 | );
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/lib/scoped_models/app_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:scoped_model/scoped_model.dart';
2 |
3 | import 'package:flutter_todo/scoped_models/connected_model.dart';
4 |
5 | class AppModel extends Model
6 | with CoreModel, TodosModel, UserModel, SettingsModel {}
7 |
--------------------------------------------------------------------------------
/lib/scoped_models/connected_model.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:http/http.dart' as http;
5 | import 'package:scoped_model/scoped_model.dart';
6 | import 'package:shared_preferences/shared_preferences.dart';
7 | import 'package:rxdart/subjects.dart';
8 |
9 | import 'package:flutter_todo/.env.dart';
10 | import 'package:flutter_todo/models/user.dart';
11 | import 'package:flutter_todo/models/filter.dart';
12 | import 'package:flutter_todo/models/priority.dart';
13 | import 'package:flutter_todo/models/todo.dart';
14 | import 'package:flutter_todo/models/settings.dart';
15 | import 'package:flutter_todo/widgets/helpers/priority_helper.dart';
16 |
17 | mixin CoreModel on Model {
18 | List _todos = [];
19 | Todo _todo;
20 | bool _isLoading = false;
21 | Filter _filter = Filter.All;
22 | User _user;
23 | }
24 |
25 | mixin TodosModel on CoreModel {
26 | List get todos {
27 | switch (_filter) {
28 | case Filter.All:
29 | return List.from(_todos);
30 |
31 | case Filter.Done:
32 | return List.from(_todos.where((todo) => todo.isDone));
33 |
34 | case Filter.NotDone:
35 | return List.from(_todos.where((todo) => !todo.isDone));
36 | }
37 |
38 | return List.from(_todos);
39 | }
40 |
41 | Filter get filter {
42 | return _filter;
43 | }
44 |
45 | bool get isLoading {
46 | return _isLoading;
47 | }
48 |
49 | Todo get currentTodo {
50 | return _todo;
51 | }
52 |
53 | void applyFilter(Filter filter) {
54 | _filter = filter;
55 | notifyListeners();
56 | }
57 |
58 | void setCurrentTodo(Todo todo) {
59 | _todo = todo;
60 | }
61 |
62 | Future fetchTodos() async {
63 | _isLoading = true;
64 | notifyListeners();
65 |
66 | try {
67 | final http.Response response = await http.get(
68 | '${Configure.FirebaseUrl}/todos.json?auth=${_user.token}&orderBy="userId"&equalTo="${_user.id}"');
69 |
70 | if (response.statusCode != 200 && response.statusCode != 201) {
71 | _isLoading = false;
72 | notifyListeners();
73 |
74 | return;
75 | }
76 |
77 | final Map todoListData = json.decode(response.body);
78 |
79 | if (todoListData == null) {
80 | _isLoading = false;
81 | notifyListeners();
82 |
83 | return;
84 | }
85 |
86 | todoListData.forEach((String todoId, dynamic todoData) {
87 | final Todo todo = Todo(
88 | id: todoId,
89 | title: todoData['title'],
90 | content: todoData['content'],
91 | priority: PriorityHelper.toPriority(todoData['priority']),
92 | isDone: todoData['isDone'],
93 | userId: _user.id,
94 | );
95 |
96 | _todos.add(todo);
97 | });
98 |
99 | _isLoading = false;
100 | notifyListeners();
101 | } catch (error) {
102 | _isLoading = false;
103 | notifyListeners();
104 | }
105 | }
106 |
107 | Future createTodo(
108 | String title, String content, Priority priority, bool isDone) async {
109 | _isLoading = true;
110 | notifyListeners();
111 |
112 | final Map formData = {
113 | 'title': title,
114 | 'content': content,
115 | 'priority': priority.toString(),
116 | 'isDone': isDone,
117 | 'userId': _user.id,
118 | };
119 |
120 | try {
121 | final http.Response response = await http.post(
122 | '${Configure.FirebaseUrl}/todos.json?auth=${_user.token}',
123 | body: json.encode(formData),
124 | );
125 |
126 | if (response.statusCode != 200 && response.statusCode != 201) {
127 | _isLoading = false;
128 | notifyListeners();
129 |
130 | return false;
131 | }
132 |
133 | final Map responseData = json.decode(response.body);
134 |
135 | Todo todo = Todo(
136 | id: responseData['name'],
137 | title: title,
138 | content: content,
139 | priority: priority,
140 | isDone: isDone,
141 | userId: _user.id,
142 | );
143 | _todos.add(todo);
144 |
145 | _isLoading = false;
146 | notifyListeners();
147 |
148 | return true;
149 | } catch (error) {
150 | _isLoading = false;
151 | notifyListeners();
152 |
153 | return false;
154 | }
155 | }
156 |
157 | Future updateTodo(
158 | String title, String content, Priority priority, bool isDone) async {
159 | _isLoading = true;
160 | notifyListeners();
161 |
162 | final Map formData = {
163 | 'title': title,
164 | 'content': content,
165 | 'priority': priority.toString(),
166 | 'isDone': isDone,
167 | 'userId': _user.id,
168 | };
169 |
170 | try {
171 | final http.Response response = await http.put(
172 | '${Configure.FirebaseUrl}/todos/${currentTodo.id}.json?auth=${_user.token}',
173 | body: json.encode(formData),
174 | );
175 |
176 | if (response.statusCode != 200 && response.statusCode != 201) {
177 | _isLoading = false;
178 | notifyListeners();
179 |
180 | return false;
181 | }
182 |
183 | Todo todo = Todo(
184 | id: currentTodo.id,
185 | title: title,
186 | content: content,
187 | priority: priority,
188 | isDone: isDone,
189 | userId: _user.id,
190 | );
191 | int todoIndex = _todos.indexWhere((t) => t.id == currentTodo.id);
192 | _todos[todoIndex] = todo;
193 |
194 | _isLoading = false;
195 | notifyListeners();
196 |
197 | return true;
198 | } catch (error) {
199 | _isLoading = false;
200 | notifyListeners();
201 |
202 | return false;
203 | }
204 | }
205 |
206 | Future removeTodo(String id) async {
207 | _isLoading = true;
208 | notifyListeners();
209 |
210 | try {
211 | Todo todo = _todos.firstWhere((t) => t.id == id);
212 | int todoIndex = _todos.indexWhere((t) => t.id == id);
213 | _todos.removeAt(todoIndex);
214 |
215 | final http.Response response = await http.delete(
216 | '${Configure.FirebaseUrl}/todos/$id.json?auth=${_user.token}');
217 |
218 | if (response.statusCode != 200 && response.statusCode != 201) {
219 | _todos[todoIndex] = todo;
220 |
221 | _isLoading = false;
222 | notifyListeners();
223 |
224 | return false;
225 | }
226 |
227 | _isLoading = false;
228 | notifyListeners();
229 |
230 | return true;
231 | } catch (error) {
232 | _isLoading = false;
233 | notifyListeners();
234 |
235 | return false;
236 | }
237 | }
238 |
239 | Future toggleDone(String id) async {
240 | _isLoading = true;
241 | notifyListeners();
242 |
243 | Todo todo = _todos.firstWhere((t) => t.id == id);
244 |
245 | final Map formData = {
246 | 'title': todo.title,
247 | 'content': todo.content,
248 | 'priority': todo.priority.toString(),
249 | 'isDone': !todo.isDone,
250 | 'userId': _user.id,
251 | };
252 |
253 | try {
254 | final http.Response response = await http.put(
255 | '${Configure.FirebaseUrl}/todos/$id.json?auth=${_user.token}',
256 | body: json.encode(formData),
257 | );
258 |
259 | if (response.statusCode != 200 && response.statusCode != 201) {
260 | _isLoading = false;
261 | notifyListeners();
262 |
263 | return false;
264 | }
265 |
266 | todo = Todo(
267 | id: todo.id,
268 | title: todo.title,
269 | content: todo.content,
270 | priority: todo.priority,
271 | isDone: !todo.isDone,
272 | userId: _user.id,
273 | );
274 | int todoIndex = _todos.indexWhere((t) => t.id == id);
275 | _todos[todoIndex] = todo;
276 |
277 | _isLoading = false;
278 | notifyListeners();
279 |
280 | return true;
281 | } catch (error) {
282 | _isLoading = false;
283 | notifyListeners();
284 |
285 | return false;
286 | }
287 | }
288 | }
289 |
290 | mixin UserModel on CoreModel {
291 | Timer _authTimer;
292 | PublishSubject _userSubject = PublishSubject();
293 |
294 | User get user {
295 | return _user;
296 | }
297 |
298 | PublishSubject get userSubject {
299 | return _userSubject;
300 | }
301 |
302 | Future