├── .firebaserc
├── .github
└── workflows
│ ├── check.yml
│ └── deploy.yml
├── .gitignore
├── .metadata
├── COPYING
├── README.md
├── analysis_options.yml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── xinthink
│ │ │ │ └── fltkeep
│ │ │ │ └── MainActivity.kt
│ │ └── 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
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── artworks
├── flt_keep.svg
├── fltkeep_screenshots.jpg
└── web-screenshots.jpg
├── assets
├── .gitignore
├── fonts
│ └── app-icons.ttf
└── images
│ ├── 1.5x
│ ├── google.png
│ └── thumbtack_intro.png
│ ├── 2.0x
│ ├── google.png
│ └── thumbtack_intro.png
│ ├── 3.0x
│ ├── google.png
│ └── thumbtack_intro.png
│ ├── 4.0x
│ ├── google.png
│ └── thumbtack_intro.png
│ ├── google.png
│ └── thumbtack_intro.png
├── firebase.json
├── functions
├── .gitignore
├── package.json
├── src
│ ├── index.ts
│ └── types.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── icons.dart
├── main.dart
├── model
│ ├── filter.dart
│ ├── note.dart
│ └── user.dart
├── models.dart
├── screen
│ ├── home_screen.dart
│ ├── login_screen.dart
│ ├── note_editor.dart
│ └── settings_screen.dart
├── screens.dart
├── service
│ └── notes_service.dart
├── services.dart
├── styles.dart
├── utils.dart
├── widget
│ ├── color_picker.dart
│ ├── drawer.dart
│ ├── drawer_filter.dart
│ ├── note_actions.dart
│ ├── note_item.dart
│ ├── notes_grid.dart
│ └── notes_list.dart
└── widgets.dart
├── pubspec.lock
├── pubspec.yaml
├── test
└── widget_test.dart
├── tool
├── arrange_images
└── pkgs
└── web
├── config-firestore.js
└── index.html
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "fltflynotes"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: check
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | check:
7 | runs-on: macos-latest
8 | steps:
9 | - uses: actions/checkout@v1
10 | - uses: subosito/flutter-action@v1
11 | with:
12 | channel: dev
13 | - name: config flutter
14 | run: |
15 | flutter --version
16 | flutter config --enable-web
17 | - name: setup configuration files
18 | run: |
19 | echo "${{ secrets.LocalDart }}" | base64 --decode > lib/local.dart
20 | - name: analyze & test
21 | run: |
22 | flutter analyze
23 | flutter test
24 | - name: notification
25 | if: cancelled() == false
26 | uses: xinthink/action-telegram@v1.1
27 | with:
28 | botToken: ${{ secrets.TelegramBotToken }}
29 | chatId: ${{ secrets.TelegramTarget }}
30 | jobStatus: ${{ job.status }}
31 |
32 | check-cloud-functions:
33 | runs-on: ubuntu-latest
34 | steps:
35 | - uses: actions/checkout@v1
36 | - uses: actions/setup-node@v1
37 | with:
38 | node-version: '8.x'
39 | - name: build & lint
40 | run: |
41 | cd functions
42 | yarn install
43 | yarn build && yarn lint
44 | - name: notification
45 | if: cancelled() == false
46 | uses: xinthink/action-telegram@v1.1
47 | with:
48 | botToken: ${{ secrets.TelegramBotToken }}
49 | chatId: ${{ secrets.TelegramTarget }}
50 | jobStatus: ${{ job.status }}
51 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: deploy
2 |
3 | on:
4 | push:
5 | branches: master
6 |
7 | jobs:
8 | deploy:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v1
12 | - uses: actions/setup-node@v1
13 | with:
14 | node-version: '8.x'
15 | - uses: subosito/flutter-action@v1
16 | with:
17 | channel: dev
18 | - name: Install js tools & depencies
19 | run: |
20 | sudo npm i -g firebase-tools
21 | sudo chown -R $USER:$GROUP ~/.npm
22 | sudo chown -R $USER:$GROUP ~/.config
23 | cd functions && yarn
24 | - name: Build flutter web
25 | run: |
26 | flutter config --enable-web
27 | flutter pub get
28 | flutter build web
29 | - name: Deploy to Firebase
30 | run: |
31 | cp -r build/web ./public
32 | firebase deploy --token "${{ secrets.FIREBASE_TOKEN }}"
33 | - name: Notification
34 | if: cancelled() == false
35 | uses: xinthink/action-telegram@v1.1
36 | with:
37 | botToken: ${{ secrets.TelegramBotToken }}
38 | chatId: ${{ secrets.TelegramTarget }}
39 | jobStatus: ${{ job.status }}
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | bak/
12 |
13 |
14 | # Project-specific
15 | local.dart
16 | **/GoogleService-Info.plist
17 | **/google-services.json
18 | web/init-firebase.js
19 | artworks/*.mp4
20 |
21 | # IntelliJ related
22 | *.iml
23 | *.ipr
24 | *.iws
25 | .idea/
26 |
27 | # The .vscode folder contains launch configuration and tasks you configure in
28 | # VS Code which you may wish to be included in version control, so this line
29 | # is commented out by default.
30 | .vscode/
31 |
32 | # Flutter/Dart/Pub related
33 | **/doc/api/
34 | .dart_tool/
35 | .flutter-plugins
36 | .flutter-plugins-dependencies
37 | .packages
38 | .pub-cache/
39 | .pub/
40 | /build/
41 |
42 | # Web related
43 | lib/generated_plugin_registrant.dart
44 |
45 | # Exceptions to above rules.
46 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
47 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 41a911099b7d06f7b1c29f4420cfcfe41fd26e46
8 | channel: unknown
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ---
4 |
5 | [![Check Status][check-badge]][check-link]
6 | [![MIT][license-badge]][license]
7 |
8 | This is a simplified [Google Keep] mobile app 'clone' built with [Flutter] + [Firebase]. For practicing prototyping and Flutter skills only.
9 |
10 | If you love to, please read the [series of stories] on Medium to find out how to create an app like this from scratch.
11 |
12 | Screenshots:
13 |
14 | [](https://youtu.be/GXNXodzgbcM)
15 |
16 | Web app screenshots:
17 |
18 | [][web app]
19 |
20 | [web app]: https://fltkeep.xinthink.com
21 | [Flutter]: https://flutter.dev
22 | [Firebase]: https://firebase.google.com/
23 | [Google Keep]: https://www.google.com/keep/
24 | [check-badge]: https://github.com/xinthink/flutter-keep/workflows/check/badge.svg
25 | [check-link]: https://github.com/xinthink/flutter-keep/actions?query=workflow%3Acheck
26 | [license-badge]: https://img.shields.io/github/license/xinthink/flutter-keep
27 | [license]: https://github.com/xinthink/flutter-keep/blob/master/COPYING
28 | [series of stories]: https://medium.com/flutter-community/build-a-note-taking-app-with-flutter-firebase-part-i-53816e7a3788
29 |
--------------------------------------------------------------------------------
/analysis_options.yml:
--------------------------------------------------------------------------------
1 | include: package:pedantic/analysis_options.yaml
2 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /local.properties
5 | **/bak/
6 | **/*.log
7 | .externalNativeBuild
8 | GeneratedPluginRegistrant.java
9 |
10 | # Project-specific
11 | google-services.json
12 | keystore.properties
13 | play-dev-*.json
14 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 28
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "com.xinthink.fltkeep"
42 | minSdkVersion 16
43 | targetSdkVersion 28
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
47 | }
48 |
49 | buildTypes {
50 | debug {
51 | minifyEnabled true
52 | }
53 | release {
54 | // TODO: Add your own signing config for the release build.
55 | // Signing with the debug keys for now, so `flutter run --release` works.
56 | signingConfig signingConfigs.debug
57 |
58 | minifyEnabled true
59 | shrinkResources true
60 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
61 | }
62 | }
63 | }
64 |
65 | flutter {
66 | source '../..'
67 | }
68 |
69 | dependencies {
70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
71 | testImplementation 'junit:junit:4.12'
72 | androidTestImplementation 'androidx.test:runner:1.1.1'
73 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
74 | }
75 |
76 | apply plugin: 'com.google.gms.google-services'
77 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/xinthink/fltkeep/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.xinthink.fltkeep
2 |
3 | import androidx.annotation.NonNull
4 | import io.flutter.embedding.android.FlutterActivity
5 | import io.flutter.embedding.engine.FlutterEngine
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity : FlutterActivity() {
9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
10 | GeneratedPluginRegistrant.registerWith(flutterEngine)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.3'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | classpath 'com.google.gms:google-services:4.3.3'
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | jcenter()
19 | }
20 | }
21 |
22 | rootProject.buildDir = '../build'
23 | subprojects {
24 | project.buildDir = "${rootProject.buildDir}/${project.name}"
25 | }
26 | subprojects {
27 | project.evaluationDependsOn(':app')
28 | }
29 |
30 | task clean(type: Delete) {
31 | delete rootProject.buildDir
32 | }
33 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-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 |
--------------------------------------------------------------------------------
/artworks/flt_keep.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/artworks/fltkeep_screenshots.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/artworks/fltkeep_screenshots.jpg
--------------------------------------------------------------------------------
/artworks/web-screenshots.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/artworks/web-screenshots.jpg
--------------------------------------------------------------------------------
/assets/.gitignore:
--------------------------------------------------------------------------------
1 | app-icons/
2 |
--------------------------------------------------------------------------------
/assets/fonts/app-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/fonts/app-icons.ttf
--------------------------------------------------------------------------------
/assets/images/1.5x/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/1.5x/google.png
--------------------------------------------------------------------------------
/assets/images/1.5x/thumbtack_intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/1.5x/thumbtack_intro.png
--------------------------------------------------------------------------------
/assets/images/2.0x/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/2.0x/google.png
--------------------------------------------------------------------------------
/assets/images/2.0x/thumbtack_intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/2.0x/thumbtack_intro.png
--------------------------------------------------------------------------------
/assets/images/3.0x/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/3.0x/google.png
--------------------------------------------------------------------------------
/assets/images/3.0x/thumbtack_intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/3.0x/thumbtack_intro.png
--------------------------------------------------------------------------------
/assets/images/4.0x/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/4.0x/google.png
--------------------------------------------------------------------------------
/assets/images/4.0x/thumbtack_intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/4.0x/thumbtack_intro.png
--------------------------------------------------------------------------------
/assets/images/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/google.png
--------------------------------------------------------------------------------
/assets/images/thumbtack_intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/assets/images/thumbtack_intro.png
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": {
3 | "predeploy": [
4 | "npm --prefix \"$RESOURCE_DIR\" run lint",
5 | "npm --prefix \"$RESOURCE_DIR\" run build"
6 | ],
7 | "source": "functions"
8 | },
9 | "hosting": {
10 | "public": "public",
11 | "ignore": [
12 | "firebase.json",
13 | "**/.*",
14 | "**/node_modules/**"
15 | ],
16 | "rewrites": [
17 | {
18 | "source": "**",
19 | "destination": "/index.html"
20 | }
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/functions/.gitignore:
--------------------------------------------------------------------------------
1 | ## Compiled JavaScript files
2 | **/*.js
3 | **/*.js.map
4 |
5 | # Typescript v1 declaration files
6 | typings/
7 |
8 | node_modules/
9 |
10 | .node-version
11 |
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "scripts": {
4 | "lint": "tslint --project tsconfig.json",
5 | "build": "tsc",
6 | "serve": "npm run build && firebase serve --only functions",
7 | "shell": "npm run build && firebase functions:shell",
8 | "start": "npm run shell",
9 | "deploy": "firebase deploy --only functions",
10 | "logs": "firebase functions:log"
11 | },
12 | "engines": {
13 | "node": "8"
14 | },
15 | "main": "lib/index.js",
16 | "dependencies": {
17 | "@google-cloud/firestore": "^3.5.1",
18 | "firebase-admin": "^8.6.0",
19 | "firebase-functions": "^3.3.0"
20 | },
21 | "devDependencies": {
22 | "@firebase/app-types": "^0.5.1",
23 | "firebase-functions-test": "^0.1.6",
24 | "firebase-tools": "^7.13.1",
25 | "tslint": "^5.12.0",
26 | "typescript": "^3.2.2"
27 | },
28 | "private": true
29 | }
30 |
--------------------------------------------------------------------------------
/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as admin from 'firebase-admin';
2 | import * as functions from 'firebase-functions';
3 |
4 | admin.initializeApp();
5 | const firestoreAdmin = new admin.firestore.v1.FirestoreAdminClient();
6 | const projectId = 'fltflynotes';
7 |
8 | /**
9 | * Trigger on user creation.
10 | */
11 | exports.createIndex = functions.auth.user().onCreate((user) => createIndex(user.uid));
12 |
13 | /**
14 | * Create notes collection index for the user.
15 | * @param uid User id
16 | */
17 | async function createIndex(uid: string): Promise {
18 | const index: Index = {
19 | fields: [
20 | {
21 | fieldPath: 'state',
22 | order: 'DESCENDING',
23 | }, {
24 | fieldPath: 'createdAt',
25 | order: 'DESCENDING',
26 | },
27 | ],
28 | queryScope: 'COLLECTION',
29 | };
30 | const operations = await firestoreAdmin.createIndex({
31 | index,
32 | parent: `projects/${projectId}/databases/(default)/collectionGroups/notes-${uid}`,
33 | });
34 | const indexId = operations[0].metadata.index;
35 | console.log('index created:', indexId);
36 | return indexId;
37 | }
38 |
39 | /**
40 | * Testing only. List indexes of a given collection.
41 | *
42 | * Query params:
43 | * - db: database id, for now, it should be: (default)
44 | * - uid: user id
45 | * - filter
46 | */
47 | exports.indexes = functions.https.onRequest((req, res) =>
48 | firestoreAdmin.listIndexes({
49 | parent: `projects/${projectId}/databases/${req.query.db}/collectionGroups/notes-${req.query.uid}`,
50 | filter: req.query.filter,
51 | }).then((data: any) => {
52 | console.log('got indexes', data);
53 | res.send(JSON.stringify(data));
54 | }).catch((e: any) => {
55 | console.error('failed to list index', e);
56 | res.sendStatus(500);
57 | }));
58 |
59 | /** Test the [createIndex] function using http endpoint. */
60 | exports.testIndex = functions.https.onRequest(async (req, res) => {
61 | const result = await createIndex(req.query.uid);
62 | res.send(JSON.stringify(result));
63 | });
64 |
--------------------------------------------------------------------------------
/functions/src/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Firstore Admin v1
3 | *
4 | * https://firebase.google.com/docs/firestore/reference/rpc/google.firestore.admin.v1#google.firestore.admin.v1.Index
5 | * https://googleapis.dev/nodejs/firestore/latest/v1.FirestoreAdminClient.html
6 | */
7 | interface Index {
8 | fields: IndexField[];
9 |
10 | queryScope: 'COLLECTION' | 'QUERY_SCOPE_UNSPECIFIED';
11 |
12 | /** The resource name of the index. Output only. */
13 | name?: string;
14 |
15 | /** The state of the index. Output only. */
16 | state?: 'STATE_UNSPECIFIED' | 'CREATING' | 'READY' | 'NEEDS_REPAIR';
17 | }
18 |
19 | interface IndexField {
20 | /**
21 | * The path of the field.
22 | *
23 | * Must match the field path specification described by [google.firestore.v1beta1.Document.fields][fields].
24 | * Special field path `__name__` may be used by itself or at the end of a path.
25 | * `__type__` may be used only at the end of path.
26 | */
27 | fieldPath: string;
28 |
29 | order?: 'DESCENDING' | 'ASCENDING' | 'ORDER_UNSPECIFIED';
30 |
31 | /** output only? */
32 | valueMode?: 'order';
33 | }
34 |
--------------------------------------------------------------------------------
/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "noImplicitReturns": true,
5 | "noUnusedLocals": true,
6 | "outDir": "lib",
7 | "sourceMap": true,
8 | "strict": true,
9 | "target": "es2017"
10 | },
11 | "compileOnSave": true,
12 | "include": [
13 | "src"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/functions/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | // -- Strict errors --
4 | // These lint rules are likely always a good idea.
5 |
6 | // Force function overloads to be declared together. This ensures readers understand APIs.
7 | "adjacent-overload-signatures": true,
8 |
9 | // Do not allow the subtle/obscure comma operator.
10 | "ban-comma-operator": true,
11 |
12 | // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules.
13 | "no-namespace": true,
14 |
15 | // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars.
16 | "no-parameter-reassignment": true,
17 |
18 | // Force the use of ES6-style imports instead of /// imports.
19 | "no-reference": true,
20 |
21 | // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the
22 | // code currently being edited (they may be incorrectly handling a different type case that does not exist).
23 | "no-unnecessary-type-assertion": true,
24 |
25 | // Disallow nonsensical label usage.
26 | "label-position": true,
27 |
28 | // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }.
29 | "no-conditional-assignment": true,
30 |
31 | // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed).
32 | "no-construct": true,
33 |
34 | // Do not allow super() to be called twice in a constructor.
35 | "no-duplicate-super": true,
36 |
37 | // Do not allow the same case to appear more than once in a switch block.
38 | "no-duplicate-switch-case": true,
39 |
40 | // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this
41 | // rule.
42 | "no-duplicate-variable": [true, "check-parameters"],
43 |
44 | // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should
45 | // instead use a separate variable name.
46 | "no-shadowed-variable": true,
47 |
48 | // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks.
49 | "no-empty": [true, "allow-empty-catch"],
50 |
51 | // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function.
52 | // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on.
53 | "no-floating-promises": true,
54 |
55 | // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when
56 | // deployed.
57 | "no-implicit-dependencies": true,
58 |
59 | // The 'this' keyword can only be used inside of classes.
60 | "no-invalid-this": true,
61 |
62 | // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead.
63 | "no-string-throw": true,
64 |
65 | // Disallow control flow statements, such as return, continue, break, and throw in finally blocks.
66 | "no-unsafe-finally": true,
67 |
68 | // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid();
69 | "no-void-expression": [true, "ignore-arrow-function-shorthand"],
70 |
71 | // Disallow duplicate imports in the same file.
72 | "no-duplicate-imports": true,
73 |
74 |
75 | // -- Strong Warnings --
76 | // These rules should almost never be needed, but may be included due to legacy code.
77 | // They are left as a warning to avoid frustration with blocked deploys when the developer
78 | // understand the warning and wants to deploy anyway.
79 |
80 | // Warn when an empty interface is defined. These are generally not useful.
81 | "no-empty-interface": {"severity": "warning"},
82 |
83 | // Warn when an import will have side effects.
84 | "no-import-side-effect": {"severity": "warning"},
85 |
86 | // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for
87 | // most values and let for values that will change.
88 | "no-var-keyword": {"severity": "warning"},
89 |
90 | // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental.
91 | "triple-equals": {"severity": "warning"},
92 |
93 | // Warn when using deprecated APIs.
94 | "deprecation": {"severity": "warning"},
95 |
96 | // -- Light Warnings --
97 | // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info"
98 | // if TSLint supported such a level.
99 |
100 | // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array.
101 | // (Even better: check out utils like .map if transforming an array!)
102 | "prefer-for-of": {"severity": "warning"},
103 |
104 | // Warns if function overloads could be unified into a single function with optional or rest parameters.
105 | "unified-signatures": {"severity": "warning"},
106 |
107 | // Prefer const for values that will not change. This better documents code.
108 | "prefer-const": {"severity": "warning"},
109 |
110 | // Multi-line object literals and function calls should have a trailing comma. This helps avoid merge conflicts.
111 | "trailing-comma": {"severity": "warning"}
112 | },
113 |
114 | "defaultSeverity": "error"
115 | }
116 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 | *.dSYM.zip
28 | *.ipa
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def parse_KV_file(file, separator='=')
14 | file_abs_path = File.expand_path(file)
15 | if !File.exists? file_abs_path
16 | return [];
17 | end
18 | generated_key_values = {}
19 | skip_line_start_symbols = ["#", "/"]
20 | File.foreach(file_abs_path) do |line|
21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
22 | plugin = line.split(pattern=separator)
23 | if plugin.length == 2
24 | podname = plugin[0].strip()
25 | path = plugin[1].strip()
26 | podpath = File.expand_path("#{path}", file_abs_path)
27 | generated_key_values[podname] = podpath
28 | else
29 | puts "Invalid plugin specification: #{line}"
30 | end
31 | end
32 | generated_key_values
33 | end
34 |
35 | target 'Runner' do
36 | use_frameworks!
37 | use_modular_headers!
38 |
39 | # Flutter Pod
40 |
41 | copied_flutter_dir = File.join(__dir__, 'Flutter')
42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
48 |
49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
50 | unless File.exist?(generated_xcode_build_settings_path)
51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
52 | end
53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
55 |
56 | unless File.exist?(copied_framework_path)
57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
58 | end
59 | unless File.exist?(copied_podspec_path)
60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
61 | end
62 | end
63 |
64 | # Keep pod path relative so it can be checked into Podfile.lock.
65 | pod 'Flutter', :path => 'Flutter'
66 |
67 | # Plugin Pods
68 |
69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
70 | # referring to absolute paths on developers' machines.
71 | system('rm -rf .symlinks')
72 | system('mkdir -p .symlinks/plugins')
73 | plugin_pods = parse_KV_file('../.flutter-plugins')
74 | plugin_pods.each do |name, path|
75 | symlink = File.join('.symlinks', 'plugins', name)
76 | File.symlink(path, symlink)
77 | pod name, :path => File.join(symlink, 'ios')
78 | end
79 | end
80 |
81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
82 | install! 'cocoapods', :disable_input_output_paths => true
83 |
84 | post_install do |installer|
85 | installer.pods_project.targets.each do |target|
86 | target.build_configurations.each do |config|
87 | config.build_settings['ENABLE_BITCODE'] = 'NO'
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - abseil/algorithm (0.20190808):
3 | - abseil/algorithm/algorithm (= 0.20190808)
4 | - abseil/algorithm/container (= 0.20190808)
5 | - abseil/algorithm/algorithm (0.20190808)
6 | - abseil/algorithm/container (0.20190808):
7 | - abseil/algorithm/algorithm
8 | - abseil/base/core_headers
9 | - abseil/meta/type_traits
10 | - abseil/base (0.20190808):
11 | - abseil/base/atomic_hook (= 0.20190808)
12 | - abseil/base/base (= 0.20190808)
13 | - abseil/base/base_internal (= 0.20190808)
14 | - abseil/base/bits (= 0.20190808)
15 | - abseil/base/config (= 0.20190808)
16 | - abseil/base/core_headers (= 0.20190808)
17 | - abseil/base/dynamic_annotations (= 0.20190808)
18 | - abseil/base/endian (= 0.20190808)
19 | - abseil/base/log_severity (= 0.20190808)
20 | - abseil/base/malloc_internal (= 0.20190808)
21 | - abseil/base/pretty_function (= 0.20190808)
22 | - abseil/base/spinlock_wait (= 0.20190808)
23 | - abseil/base/throw_delegate (= 0.20190808)
24 | - abseil/base/atomic_hook (0.20190808)
25 | - abseil/base/base (0.20190808):
26 | - abseil/base/atomic_hook
27 | - abseil/base/base_internal
28 | - abseil/base/config
29 | - abseil/base/core_headers
30 | - abseil/base/dynamic_annotations
31 | - abseil/base/log_severity
32 | - abseil/base/spinlock_wait
33 | - abseil/meta/type_traits
34 | - abseil/base/base_internal (0.20190808):
35 | - abseil/meta/type_traits
36 | - abseil/base/bits (0.20190808):
37 | - abseil/base/core_headers
38 | - abseil/base/config (0.20190808)
39 | - abseil/base/core_headers (0.20190808):
40 | - abseil/base/config
41 | - abseil/base/dynamic_annotations (0.20190808)
42 | - abseil/base/endian (0.20190808):
43 | - abseil/base/config
44 | - abseil/base/core_headers
45 | - abseil/base/log_severity (0.20190808):
46 | - abseil/base/core_headers
47 | - abseil/base/malloc_internal (0.20190808):
48 | - abseil/base/base
49 | - abseil/base/config
50 | - abseil/base/core_headers
51 | - abseil/base/dynamic_annotations
52 | - abseil/base/spinlock_wait
53 | - abseil/base/pretty_function (0.20190808)
54 | - abseil/base/spinlock_wait (0.20190808):
55 | - abseil/base/core_headers
56 | - abseil/base/throw_delegate (0.20190808):
57 | - abseil/base/base
58 | - abseil/base/config
59 | - abseil/memory (0.20190808):
60 | - abseil/memory/memory (= 0.20190808)
61 | - abseil/memory/memory (0.20190808):
62 | - abseil/base/core_headers
63 | - abseil/meta/type_traits
64 | - abseil/meta (0.20190808):
65 | - abseil/meta/type_traits (= 0.20190808)
66 | - abseil/meta/type_traits (0.20190808):
67 | - abseil/base/config
68 | - abseil/numeric/int128 (0.20190808):
69 | - abseil/base/config
70 | - abseil/base/core_headers
71 | - abseil/strings/internal (0.20190808):
72 | - abseil/base/core_headers
73 | - abseil/base/endian
74 | - abseil/meta/type_traits
75 | - abseil/strings/strings (0.20190808):
76 | - abseil/base/base
77 | - abseil/base/bits
78 | - abseil/base/config
79 | - abseil/base/core_headers
80 | - abseil/base/endian
81 | - abseil/base/throw_delegate
82 | - abseil/memory/memory
83 | - abseil/meta/type_traits
84 | - abseil/numeric/int128
85 | - abseil/strings/internal
86 | - abseil/time (0.20190808):
87 | - abseil/time/internal (= 0.20190808)
88 | - abseil/time/time (= 0.20190808)
89 | - abseil/time/internal (0.20190808):
90 | - abseil/time/internal/cctz (= 0.20190808)
91 | - abseil/time/internal/cctz (0.20190808):
92 | - abseil/time/internal/cctz/civil_time (= 0.20190808)
93 | - abseil/time/internal/cctz/includes (= 0.20190808)
94 | - abseil/time/internal/cctz/time_zone (= 0.20190808)
95 | - abseil/time/internal/cctz/civil_time (0.20190808)
96 | - abseil/time/internal/cctz/includes (0.20190808)
97 | - abseil/time/internal/cctz/time_zone (0.20190808):
98 | - abseil/time/internal/cctz/civil_time
99 | - abseil/time/time (0.20190808):
100 | - abseil/base/base
101 | - abseil/base/core_headers
102 | - abseil/numeric/int128
103 | - abseil/strings/strings
104 | - abseil/time/internal/cctz/civil_time
105 | - abseil/time/internal/cctz/time_zone
106 | - abseil/types (0.20190808):
107 | - abseil/types/any (= 0.20190808)
108 | - abseil/types/bad_any_cast (= 0.20190808)
109 | - abseil/types/bad_any_cast_impl (= 0.20190808)
110 | - abseil/types/bad_optional_access (= 0.20190808)
111 | - abseil/types/bad_variant_access (= 0.20190808)
112 | - abseil/types/compare (= 0.20190808)
113 | - abseil/types/optional (= 0.20190808)
114 | - abseil/types/span (= 0.20190808)
115 | - abseil/types/variant (= 0.20190808)
116 | - abseil/types/any (0.20190808):
117 | - abseil/base/config
118 | - abseil/base/core_headers
119 | - abseil/meta/type_traits
120 | - abseil/types/bad_any_cast
121 | - abseil/utility/utility
122 | - abseil/types/bad_any_cast (0.20190808):
123 | - abseil/base/config
124 | - abseil/types/bad_any_cast_impl
125 | - abseil/types/bad_any_cast_impl (0.20190808):
126 | - abseil/base/base
127 | - abseil/base/config
128 | - abseil/types/bad_optional_access (0.20190808):
129 | - abseil/base/base
130 | - abseil/base/config
131 | - abseil/types/bad_variant_access (0.20190808):
132 | - abseil/base/base
133 | - abseil/base/config
134 | - abseil/types/compare (0.20190808):
135 | - abseil/base/core_headers
136 | - abseil/meta/type_traits
137 | - abseil/types/optional (0.20190808):
138 | - abseil/base/base_internal
139 | - abseil/base/config
140 | - abseil/base/core_headers
141 | - abseil/memory/memory
142 | - abseil/meta/type_traits
143 | - abseil/types/bad_optional_access
144 | - abseil/utility/utility
145 | - abseil/types/span (0.20190808):
146 | - abseil/algorithm/algorithm
147 | - abseil/base/core_headers
148 | - abseil/base/throw_delegate
149 | - abseil/meta/type_traits
150 | - abseil/types/variant (0.20190808):
151 | - abseil/base/base_internal
152 | - abseil/base/config
153 | - abseil/base/core_headers
154 | - abseil/meta/type_traits
155 | - abseil/types/bad_variant_access
156 | - abseil/utility/utility
157 | - abseil/utility/utility (0.20190808):
158 | - abseil/base/base_internal
159 | - abseil/base/config
160 | - abseil/meta/type_traits
161 | - AppAuth (1.3.0):
162 | - AppAuth/Core (= 1.3.0)
163 | - AppAuth/ExternalUserAgent (= 1.3.0)
164 | - AppAuth/Core (1.3.0)
165 | - AppAuth/ExternalUserAgent (1.3.0)
166 | - BoringSSL-GRPC (0.0.3):
167 | - BoringSSL-GRPC/Implementation (= 0.0.3)
168 | - BoringSSL-GRPC/Interface (= 0.0.3)
169 | - BoringSSL-GRPC/Implementation (0.0.3):
170 | - BoringSSL-GRPC/Interface (= 0.0.3)
171 | - BoringSSL-GRPC/Interface (0.0.3)
172 | - cloud_firestore (0.0.1):
173 | - Firebase/Core
174 | - Firebase/Firestore (~> 6.0)
175 | - Flutter
176 | - cloud_firestore_web (0.1.0):
177 | - Flutter
178 | - Firebase/Analytics (6.17.0):
179 | - Firebase/Core
180 | - Firebase/Auth (6.17.0):
181 | - Firebase/CoreOnly
182 | - FirebaseAuth (~> 6.4.3)
183 | - Firebase/Core (6.17.0):
184 | - Firebase/CoreOnly
185 | - FirebaseAnalytics (= 6.2.2)
186 | - Firebase/CoreOnly (6.17.0):
187 | - FirebaseCore (= 6.6.2)
188 | - Firebase/Firestore (6.17.0):
189 | - Firebase/CoreOnly
190 | - FirebaseFirestore (~> 1.10.2)
191 | - firebase_analytics (0.0.1):
192 | - Firebase/Analytics (~> 6.0)
193 | - Firebase/Core
194 | - Flutter
195 | - firebase_auth (0.0.1):
196 | - Firebase/Auth (~> 6.3)
197 | - Firebase/Core
198 | - Flutter
199 | - firebase_auth_web (0.1.0):
200 | - Flutter
201 | - firebase_core (0.0.1):
202 | - Firebase/Core
203 | - Flutter
204 | - firebase_core_web (0.1.0):
205 | - Flutter
206 | - FirebaseAnalytics (6.2.2):
207 | - FirebaseCore (~> 6.6)
208 | - FirebaseInstanceID (~> 4.3)
209 | - GoogleAppMeasurement (= 6.2.2)
210 | - GoogleUtilities/AppDelegateSwizzler (~> 6.0)
211 | - GoogleUtilities/MethodSwizzler (~> 6.0)
212 | - GoogleUtilities/Network (~> 6.0)
213 | - "GoogleUtilities/NSData+zlib (~> 6.0)"
214 | - nanopb (= 0.3.9011)
215 | - FirebaseAuth (6.4.3):
216 | - FirebaseAuthInterop (~> 1.0)
217 | - FirebaseCore (~> 6.6)
218 | - GoogleUtilities/AppDelegateSwizzler (~> 6.5)
219 | - GoogleUtilities/Environment (~> 6.5)
220 | - GTMSessionFetcher/Core (~> 1.1)
221 | - FirebaseAuthInterop (1.0.0)
222 | - FirebaseCore (6.6.2):
223 | - FirebaseCoreDiagnostics (~> 1.2)
224 | - FirebaseCoreDiagnosticsInterop (~> 1.2)
225 | - GoogleUtilities/Environment (~> 6.5)
226 | - GoogleUtilities/Logger (~> 6.5)
227 | - FirebaseCoreDiagnostics (1.2.0):
228 | - FirebaseCoreDiagnosticsInterop (~> 1.2)
229 | - GoogleDataTransportCCTSupport (~> 1.3)
230 | - GoogleUtilities/Environment (~> 6.5)
231 | - GoogleUtilities/Logger (~> 6.5)
232 | - nanopb (~> 0.3.901)
233 | - FirebaseCoreDiagnosticsInterop (1.2.0)
234 | - FirebaseFirestore (1.10.2):
235 | - abseil/algorithm (= 0.20190808)
236 | - abseil/base (= 0.20190808)
237 | - abseil/memory (= 0.20190808)
238 | - abseil/meta (= 0.20190808)
239 | - abseil/strings/strings (= 0.20190808)
240 | - abseil/time (= 0.20190808)
241 | - abseil/types (= 0.20190808)
242 | - FirebaseAuthInterop (~> 1.0)
243 | - FirebaseCore (~> 6.2)
244 | - "gRPC-C++ (= 0.0.9)"
245 | - leveldb-library (~> 1.22)
246 | - nanopb (~> 0.3.901)
247 | - FirebaseInstallations (1.1.0):
248 | - FirebaseCore (~> 6.6)
249 | - GoogleUtilities/UserDefaults (~> 6.5)
250 | - PromisesObjC (~> 1.2)
251 | - FirebaseInstanceID (4.3.1):
252 | - FirebaseCore (~> 6.6)
253 | - FirebaseInstallations (~> 1.0)
254 | - GoogleUtilities/Environment (~> 6.5)
255 | - GoogleUtilities/UserDefaults (~> 6.5)
256 | - Flutter (1.0.0)
257 | - google_sign_in (0.0.1):
258 | - Flutter
259 | - GoogleSignIn (~> 5.0)
260 | - google_sign_in_web (0.8.1):
261 | - Flutter
262 | - GoogleAppMeasurement (6.2.2):
263 | - GoogleUtilities/AppDelegateSwizzler (~> 6.0)
264 | - GoogleUtilities/MethodSwizzler (~> 6.0)
265 | - GoogleUtilities/Network (~> 6.0)
266 | - "GoogleUtilities/NSData+zlib (~> 6.0)"
267 | - nanopb (= 0.3.9011)
268 | - GoogleDataTransport (4.0.0)
269 | - GoogleDataTransportCCTSupport (1.4.0):
270 | - GoogleDataTransport (~> 4.0)
271 | - nanopb (~> 0.3.901)
272 | - GoogleSignIn (5.0.2):
273 | - AppAuth (~> 1.2)
274 | - GTMAppAuth (~> 1.0)
275 | - GTMSessionFetcher/Core (~> 1.1)
276 | - GoogleUtilities/AppDelegateSwizzler (6.5.1):
277 | - GoogleUtilities/Environment
278 | - GoogleUtilities/Logger
279 | - GoogleUtilities/Network
280 | - GoogleUtilities/Environment (6.5.1)
281 | - GoogleUtilities/Logger (6.5.1):
282 | - GoogleUtilities/Environment
283 | - GoogleUtilities/MethodSwizzler (6.5.1):
284 | - GoogleUtilities/Logger
285 | - GoogleUtilities/Network (6.5.1):
286 | - GoogleUtilities/Logger
287 | - "GoogleUtilities/NSData+zlib"
288 | - GoogleUtilities/Reachability
289 | - "GoogleUtilities/NSData+zlib (6.5.1)"
290 | - GoogleUtilities/Reachability (6.5.1):
291 | - GoogleUtilities/Logger
292 | - GoogleUtilities/UserDefaults (6.5.1):
293 | - GoogleUtilities/Logger
294 | - "gRPC-C++ (0.0.9)":
295 | - "gRPC-C++/Implementation (= 0.0.9)"
296 | - "gRPC-C++/Interface (= 0.0.9)"
297 | - "gRPC-C++/Implementation (0.0.9)":
298 | - "gRPC-C++/Interface (= 0.0.9)"
299 | - gRPC-Core (= 1.21.0)
300 | - nanopb (~> 0.3)
301 | - "gRPC-C++/Interface (0.0.9)"
302 | - gRPC-Core (1.21.0):
303 | - gRPC-Core/Implementation (= 1.21.0)
304 | - gRPC-Core/Interface (= 1.21.0)
305 | - gRPC-Core/Implementation (1.21.0):
306 | - BoringSSL-GRPC (= 0.0.3)
307 | - gRPC-Core/Interface (= 1.21.0)
308 | - nanopb (~> 0.3)
309 | - gRPC-Core/Interface (1.21.0)
310 | - GTMAppAuth (1.0.0):
311 | - AppAuth/Core (~> 1.0)
312 | - GTMSessionFetcher (~> 1.1)
313 | - GTMSessionFetcher (1.3.1):
314 | - GTMSessionFetcher/Full (= 1.3.1)
315 | - GTMSessionFetcher/Core (1.3.1)
316 | - GTMSessionFetcher/Full (1.3.1):
317 | - GTMSessionFetcher/Core (= 1.3.1)
318 | - leveldb-library (1.22)
319 | - nanopb (0.3.9011):
320 | - nanopb/decode (= 0.3.9011)
321 | - nanopb/encode (= 0.3.9011)
322 | - nanopb/decode (0.3.9011)
323 | - nanopb/encode (0.3.9011)
324 | - PromisesObjC (1.2.8)
325 |
326 | DEPENDENCIES:
327 | - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
328 | - cloud_firestore_web (from `.symlinks/plugins/cloud_firestore_web/ios`)
329 | - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`)
330 | - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
331 | - firebase_auth_web (from `.symlinks/plugins/firebase_auth_web/ios`)
332 | - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
333 | - firebase_core_web (from `.symlinks/plugins/firebase_core_web/ios`)
334 | - Flutter (from `Flutter`)
335 | - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`)
336 | - google_sign_in_web (from `.symlinks/plugins/google_sign_in_web/ios`)
337 |
338 | SPEC REPOS:
339 | trunk:
340 | - abseil
341 | - AppAuth
342 | - BoringSSL-GRPC
343 | - Firebase
344 | - FirebaseAnalytics
345 | - FirebaseAuth
346 | - FirebaseAuthInterop
347 | - FirebaseCore
348 | - FirebaseCoreDiagnostics
349 | - FirebaseCoreDiagnosticsInterop
350 | - FirebaseFirestore
351 | - FirebaseInstallations
352 | - FirebaseInstanceID
353 | - GoogleAppMeasurement
354 | - GoogleDataTransport
355 | - GoogleDataTransportCCTSupport
356 | - GoogleSignIn
357 | - GoogleUtilities
358 | - "gRPC-C++"
359 | - gRPC-Core
360 | - GTMAppAuth
361 | - GTMSessionFetcher
362 | - leveldb-library
363 | - nanopb
364 | - PromisesObjC
365 |
366 | EXTERNAL SOURCES:
367 | cloud_firestore:
368 | :path: ".symlinks/plugins/cloud_firestore/ios"
369 | cloud_firestore_web:
370 | :path: ".symlinks/plugins/cloud_firestore_web/ios"
371 | firebase_analytics:
372 | :path: ".symlinks/plugins/firebase_analytics/ios"
373 | firebase_auth:
374 | :path: ".symlinks/plugins/firebase_auth/ios"
375 | firebase_auth_web:
376 | :path: ".symlinks/plugins/firebase_auth_web/ios"
377 | firebase_core:
378 | :path: ".symlinks/plugins/firebase_core/ios"
379 | firebase_core_web:
380 | :path: ".symlinks/plugins/firebase_core_web/ios"
381 | Flutter:
382 | :path: Flutter
383 | google_sign_in:
384 | :path: ".symlinks/plugins/google_sign_in/ios"
385 | google_sign_in_web:
386 | :path: ".symlinks/plugins/google_sign_in_web/ios"
387 |
388 | SPEC CHECKSUMS:
389 | abseil: 18063d773f5366ff8736a050fe035a28f635fd27
390 | AppAuth: 73574f3013a1e65b9601a3ddc8b3158cce68c09d
391 | BoringSSL-GRPC: db8764df3204ccea016e1c8dd15d9a9ad63ff318
392 | cloud_firestore: 11c87f1d0b4193831045a320aed50723b67f2ea3
393 | cloud_firestore_web: 9ec3dc7f5f98de5129339802d491c1204462bfec
394 | Firebase: 2672e5636b42f977177716317e68346ae5e9de25
395 | firebase_analytics: dacdcfc524d722fff13dcff942f0dfa47e6be567
396 | firebase_auth: 1f46484071219159ad6c64d304af425ad7373052
397 | firebase_auth_web: 0955c07bcc06e84af76b9d4e32e6f31518f2d7de
398 | firebase_core: dc539d4bdc69894823e4a6e557034a9e48960f21
399 | firebase_core_web: d501d8b946b60c8af265428ce483b0fff5ad52d1
400 | FirebaseAnalytics: cf95d3aab897612783020fbd98401d5366f135ee
401 | FirebaseAuth: 5ce2b03a3d7fe56b7a6e4c5ec7ff1522890b1d6f
402 | FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc
403 | FirebaseCore: a1dd9dd6355a8356dd30a8f076839e242285a81c
404 | FirebaseCoreDiagnostics: 5e78803ab276bc5b50340e3c539c06c3de35c649
405 | FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850
406 | FirebaseFirestore: 2ee644acccde18e49b2fb5d434c709b1f1171b13
407 | FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b
408 | FirebaseInstanceID: 031d7d0c8e7b5c030bbeb4d2a690550375e83fec
409 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
410 | google_sign_in: decaf71f56e22fb1dc179de062177ae6df415716
411 | google_sign_in_web: 52deb24929ac0992baff65c57956031c44ed44c3
412 | GoogleAppMeasurement: d0560d915abf15e692e8538ba1d58442217b6aff
413 | GoogleDataTransport: 47fe3b8e1673e5187dfd615656a3c5034f150d69
414 | GoogleDataTransportCCTSupport: 36f69887fd212db6d7ef4dd45ba44523717a434e
415 | GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213
416 | GoogleUtilities: 06eb53bb579efe7099152735900dd04bf09e7275
417 | "gRPC-C++": 9dfe7b44821e7b3e44aacad2af29d2c21f7cde83
418 | gRPC-Core: c9aef9a261a1247e881b18059b84d597293c9947
419 | GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e
420 | GTMSessionFetcher: cea130bbfe5a7edc8d06d3f0d17288c32ffe9925
421 | leveldb-library: 55d93ee664b4007aac644a782d11da33fba316f7
422 | nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd
423 | PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6
424 |
425 | PODFILE CHECKSUM: 083258d7f5e80b42ea9bfee905fe93049bc04c64
426 |
427 | COCOAPODS: 1.8.4
428 |
--------------------------------------------------------------------------------
/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 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
15 | 8A4E6BCC23FE5E1B00A8D172 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8A4E6BCB23FE5E1B00A8D172 /* GoogleService-Info.plist */; };
16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
18 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
19 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
20 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
21 | D8FAC2AD9BE63AD3D75AA993 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99E198A1C7918B0B28C2F462 /* Pods_Runner.framework */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXCopyFilesBuildPhase section */
25 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
26 | isa = PBXCopyFilesBuildPhase;
27 | buildActionMask = 2147483647;
28 | dstPath = "";
29 | dstSubfolderSpec = 10;
30 | files = (
31 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
32 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
33 | );
34 | name = "Embed Frameworks";
35 | runOnlyForDeploymentPostprocessing = 0;
36 | };
37 | /* End PBXCopyFilesBuildPhase section */
38 |
39 | /* Begin PBXFileReference section */
40 | 0A5E3762AAFA8C51B233F557 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
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 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
46 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
47 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
48 | 81CDC34B9D3EFB418E5E053E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
49 | 8A4E6BCB23FE5E1B00A8D172 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; 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 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
58 | 99E198A1C7918B0B28C2F462 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
59 | CBF781A5A1044F9DD193CECD /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.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 | D8FAC2AD9BE63AD3D75AA993 /* Pods_Runner.framework in Frameworks */,
70 | );
71 | runOnlyForDeploymentPostprocessing = 0;
72 | };
73 | /* End PBXFrameworksBuildPhase section */
74 |
75 | /* Begin PBXGroup section */
76 | 9740EEB11CF90186004384FC /* Flutter */ = {
77 | isa = PBXGroup;
78 | children = (
79 | 3B80C3931E831B6300D905FE /* App.framework */,
80 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
81 | 9740EEBA1CF902C7004384FC /* Flutter.framework */,
82 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
83 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
84 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
85 | );
86 | name = Flutter;
87 | sourceTree = "";
88 | };
89 | 97C146E51CF9000F007C117D = {
90 | isa = PBXGroup;
91 | children = (
92 | 9740EEB11CF90186004384FC /* Flutter */,
93 | 97C146F01CF9000F007C117D /* Runner */,
94 | 97C146EF1CF9000F007C117D /* Products */,
95 | 9AD6B05DC04E7FFFB48A5C1C /* Pods */,
96 | ACDB9CD9458A1597CC245FC4 /* Frameworks */,
97 | );
98 | sourceTree = "";
99 | };
100 | 97C146EF1CF9000F007C117D /* Products */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 97C146EE1CF9000F007C117D /* Runner.app */,
104 | );
105 | name = Products;
106 | sourceTree = "";
107 | };
108 | 97C146F01CF9000F007C117D /* Runner */ = {
109 | isa = PBXGroup;
110 | children = (
111 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
112 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
113 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
114 | 97C147021CF9000F007C117D /* Info.plist */,
115 | 97C146F11CF9000F007C117D /* Supporting Files */,
116 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
117 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
118 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
119 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
120 | );
121 | path = Runner;
122 | sourceTree = "";
123 | };
124 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
125 | isa = PBXGroup;
126 | children = (
127 | 8A4E6BCB23FE5E1B00A8D172 /* GoogleService-Info.plist */,
128 | );
129 | name = "Supporting Files";
130 | sourceTree = "";
131 | };
132 | 9AD6B05DC04E7FFFB48A5C1C /* Pods */ = {
133 | isa = PBXGroup;
134 | children = (
135 | 0A5E3762AAFA8C51B233F557 /* Pods-Runner.debug.xcconfig */,
136 | 81CDC34B9D3EFB418E5E053E /* Pods-Runner.release.xcconfig */,
137 | CBF781A5A1044F9DD193CECD /* Pods-Runner.profile.xcconfig */,
138 | );
139 | path = Pods;
140 | sourceTree = "";
141 | };
142 | ACDB9CD9458A1597CC245FC4 /* Frameworks */ = {
143 | isa = PBXGroup;
144 | children = (
145 | 99E198A1C7918B0B28C2F462 /* Pods_Runner.framework */,
146 | );
147 | name = Frameworks;
148 | sourceTree = "";
149 | };
150 | /* End PBXGroup section */
151 |
152 | /* Begin PBXNativeTarget section */
153 | 97C146ED1CF9000F007C117D /* Runner */ = {
154 | isa = PBXNativeTarget;
155 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
156 | buildPhases = (
157 | DBE83B6E1A0D25D939A56B8F /* [CP] Check Pods Manifest.lock */,
158 | 9740EEB61CF901F6004384FC /* Run Script */,
159 | 97C146EA1CF9000F007C117D /* Sources */,
160 | 97C146EB1CF9000F007C117D /* Frameworks */,
161 | 97C146EC1CF9000F007C117D /* Resources */,
162 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
163 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
164 | 4E7940F264450147D0DE0CF3 /* [CP] Embed Pods Frameworks */,
165 | ED66225536E1E85DCAB23F66 /* [CP] Copy Pods Resources */,
166 | );
167 | buildRules = (
168 | );
169 | dependencies = (
170 | );
171 | name = Runner;
172 | productName = Runner;
173 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
174 | productType = "com.apple.product-type.application";
175 | };
176 | /* End PBXNativeTarget section */
177 |
178 | /* Begin PBXProject section */
179 | 97C146E61CF9000F007C117D /* Project object */ = {
180 | isa = PBXProject;
181 | attributes = {
182 | LastUpgradeCheck = 1020;
183 | ORGANIZATIONNAME = "";
184 | TargetAttributes = {
185 | 97C146ED1CF9000F007C117D = {
186 | CreatedOnToolsVersion = 7.3.1;
187 | DevelopmentTeam = KEYR6LAUBK;
188 | LastSwiftMigration = 1100;
189 | ProvisioningStyle = Manual;
190 | };
191 | };
192 | };
193 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
194 | compatibilityVersion = "Xcode 3.2";
195 | developmentRegion = en;
196 | hasScannedForEncodings = 0;
197 | knownRegions = (
198 | en,
199 | Base,
200 | );
201 | mainGroup = 97C146E51CF9000F007C117D;
202 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
203 | projectDirPath = "";
204 | projectRoot = "";
205 | targets = (
206 | 97C146ED1CF9000F007C117D /* Runner */,
207 | );
208 | };
209 | /* End PBXProject section */
210 |
211 | /* Begin PBXResourcesBuildPhase section */
212 | 97C146EC1CF9000F007C117D /* Resources */ = {
213 | isa = PBXResourcesBuildPhase;
214 | buildActionMask = 2147483647;
215 | files = (
216 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
217 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
218 | 8A4E6BCC23FE5E1B00A8D172 /* GoogleService-Info.plist in Resources */,
219 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
220 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
221 | );
222 | runOnlyForDeploymentPostprocessing = 0;
223 | };
224 | /* End PBXResourcesBuildPhase section */
225 |
226 | /* Begin PBXShellScriptBuildPhase section */
227 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
228 | isa = PBXShellScriptBuildPhase;
229 | buildActionMask = 2147483647;
230 | files = (
231 | );
232 | inputPaths = (
233 | );
234 | name = "Thin Binary";
235 | outputPaths = (
236 | );
237 | runOnlyForDeploymentPostprocessing = 0;
238 | shellPath = /bin/sh;
239 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
240 | };
241 | 4E7940F264450147D0DE0CF3 /* [CP] Embed Pods Frameworks */ = {
242 | isa = PBXShellScriptBuildPhase;
243 | buildActionMask = 2147483647;
244 | files = (
245 | );
246 | inputPaths = (
247 | );
248 | name = "[CP] Embed Pods Frameworks";
249 | outputPaths = (
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | shellPath = /bin/sh;
253 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
254 | showEnvVarsInLog = 0;
255 | };
256 | 9740EEB61CF901F6004384FC /* Run Script */ = {
257 | isa = PBXShellScriptBuildPhase;
258 | buildActionMask = 2147483647;
259 | files = (
260 | );
261 | inputPaths = (
262 | );
263 | name = "Run Script";
264 | outputPaths = (
265 | );
266 | runOnlyForDeploymentPostprocessing = 0;
267 | shellPath = /bin/sh;
268 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
269 | };
270 | DBE83B6E1A0D25D939A56B8F /* [CP] Check Pods Manifest.lock */ = {
271 | isa = PBXShellScriptBuildPhase;
272 | buildActionMask = 2147483647;
273 | files = (
274 | );
275 | inputFileListPaths = (
276 | );
277 | inputPaths = (
278 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
279 | "${PODS_ROOT}/Manifest.lock",
280 | );
281 | name = "[CP] Check Pods Manifest.lock";
282 | outputFileListPaths = (
283 | );
284 | outputPaths = (
285 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
286 | );
287 | runOnlyForDeploymentPostprocessing = 0;
288 | shellPath = /bin/sh;
289 | 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";
290 | showEnvVarsInLog = 0;
291 | };
292 | ED66225536E1E85DCAB23F66 /* [CP] Copy Pods Resources */ = {
293 | isa = PBXShellScriptBuildPhase;
294 | buildActionMask = 2147483647;
295 | files = (
296 | );
297 | inputPaths = (
298 | );
299 | name = "[CP] Copy Pods Resources";
300 | outputPaths = (
301 | );
302 | runOnlyForDeploymentPostprocessing = 0;
303 | shellPath = /bin/sh;
304 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
305 | showEnvVarsInLog = 0;
306 | };
307 | /* End PBXShellScriptBuildPhase section */
308 |
309 | /* Begin PBXSourcesBuildPhase section */
310 | 97C146EA1CF9000F007C117D /* Sources */ = {
311 | isa = PBXSourcesBuildPhase;
312 | buildActionMask = 2147483647;
313 | files = (
314 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
315 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
316 | );
317 | runOnlyForDeploymentPostprocessing = 0;
318 | };
319 | /* End PBXSourcesBuildPhase section */
320 |
321 | /* Begin PBXVariantGroup section */
322 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
323 | isa = PBXVariantGroup;
324 | children = (
325 | 97C146FB1CF9000F007C117D /* Base */,
326 | );
327 | name = Main.storyboard;
328 | sourceTree = "";
329 | };
330 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
331 | isa = PBXVariantGroup;
332 | children = (
333 | 97C147001CF9000F007C117D /* Base */,
334 | );
335 | name = LaunchScreen.storyboard;
336 | sourceTree = "";
337 | };
338 | /* End PBXVariantGroup section */
339 |
340 | /* Begin XCBuildConfiguration section */
341 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
342 | isa = XCBuildConfiguration;
343 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
344 | buildSettings = {
345 | ALWAYS_SEARCH_USER_PATHS = NO;
346 | CLANG_ANALYZER_NONNULL = YES;
347 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
348 | CLANG_CXX_LIBRARY = "libc++";
349 | CLANG_ENABLE_MODULES = YES;
350 | CLANG_ENABLE_OBJC_ARC = YES;
351 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
352 | CLANG_WARN_BOOL_CONVERSION = YES;
353 | CLANG_WARN_COMMA = YES;
354 | CLANG_WARN_CONSTANT_CONVERSION = YES;
355 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
356 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
357 | CLANG_WARN_EMPTY_BODY = YES;
358 | CLANG_WARN_ENUM_CONVERSION = YES;
359 | CLANG_WARN_INFINITE_RECURSION = YES;
360 | CLANG_WARN_INT_CONVERSION = YES;
361 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
362 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
363 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
364 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
365 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
366 | CLANG_WARN_STRICT_PROTOTYPES = YES;
367 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
368 | CLANG_WARN_UNREACHABLE_CODE = YES;
369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
370 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
371 | COPY_PHASE_STRIP = NO;
372 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
373 | ENABLE_NS_ASSERTIONS = NO;
374 | ENABLE_STRICT_OBJC_MSGSEND = YES;
375 | GCC_C_LANGUAGE_STANDARD = gnu99;
376 | GCC_NO_COMMON_BLOCKS = YES;
377 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
378 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
379 | GCC_WARN_UNDECLARED_SELECTOR = YES;
380 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
381 | GCC_WARN_UNUSED_FUNCTION = YES;
382 | GCC_WARN_UNUSED_VARIABLE = YES;
383 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
384 | MTL_ENABLE_DEBUG_INFO = NO;
385 | SDKROOT = iphoneos;
386 | SUPPORTED_PLATFORMS = iphoneos;
387 | TARGETED_DEVICE_FAMILY = "1,2";
388 | VALIDATE_PRODUCT = YES;
389 | };
390 | name = Profile;
391 | };
392 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
393 | isa = XCBuildConfiguration;
394 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
395 | buildSettings = {
396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
397 | CLANG_ENABLE_MODULES = YES;
398 | CODE_SIGN_IDENTITY = "iPhone Developer";
399 | CODE_SIGN_STYLE = Manual;
400 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
401 | DEVELOPMENT_TEAM = KEYR6LAUBK;
402 | ENABLE_BITCODE = NO;
403 | FRAMEWORK_SEARCH_PATHS = (
404 | "$(inherited)",
405 | "$(PROJECT_DIR)/Flutter",
406 | );
407 | INFOPLIST_FILE = Runner/Info.plist;
408 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
409 | LIBRARY_SEARCH_PATHS = (
410 | "$(inherited)",
411 | "$(PROJECT_DIR)/Flutter",
412 | );
413 | PRODUCT_BUNDLE_IDENTIFIER = com.xinthink.fltkeep;
414 | PRODUCT_NAME = Runner;
415 | PROVISIONING_PROFILE_SPECIFIER = "match Development com.xinthink.fltkeep";
416 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
417 | SWIFT_VERSION = 5.0;
418 | VERSIONING_SYSTEM = "apple-generic";
419 | };
420 | name = Profile;
421 | };
422 | 97C147031CF9000F007C117D /* Debug */ = {
423 | isa = XCBuildConfiguration;
424 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
425 | buildSettings = {
426 | ALWAYS_SEARCH_USER_PATHS = NO;
427 | CLANG_ANALYZER_NONNULL = YES;
428 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
429 | CLANG_CXX_LIBRARY = "libc++";
430 | CLANG_ENABLE_MODULES = YES;
431 | CLANG_ENABLE_OBJC_ARC = YES;
432 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
433 | CLANG_WARN_BOOL_CONVERSION = YES;
434 | CLANG_WARN_COMMA = YES;
435 | CLANG_WARN_CONSTANT_CONVERSION = YES;
436 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
437 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
438 | CLANG_WARN_EMPTY_BODY = YES;
439 | CLANG_WARN_ENUM_CONVERSION = YES;
440 | CLANG_WARN_INFINITE_RECURSION = YES;
441 | CLANG_WARN_INT_CONVERSION = YES;
442 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
443 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
444 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
445 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
446 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
447 | CLANG_WARN_STRICT_PROTOTYPES = YES;
448 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
449 | CLANG_WARN_UNREACHABLE_CODE = YES;
450 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
451 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
452 | COPY_PHASE_STRIP = NO;
453 | DEBUG_INFORMATION_FORMAT = dwarf;
454 | ENABLE_STRICT_OBJC_MSGSEND = YES;
455 | ENABLE_TESTABILITY = YES;
456 | GCC_C_LANGUAGE_STANDARD = gnu99;
457 | GCC_DYNAMIC_NO_PIC = NO;
458 | GCC_NO_COMMON_BLOCKS = YES;
459 | GCC_OPTIMIZATION_LEVEL = 0;
460 | GCC_PREPROCESSOR_DEFINITIONS = (
461 | "DEBUG=1",
462 | "$(inherited)",
463 | );
464 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
465 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
466 | GCC_WARN_UNDECLARED_SELECTOR = YES;
467 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
468 | GCC_WARN_UNUSED_FUNCTION = YES;
469 | GCC_WARN_UNUSED_VARIABLE = YES;
470 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
471 | MTL_ENABLE_DEBUG_INFO = YES;
472 | ONLY_ACTIVE_ARCH = YES;
473 | SDKROOT = iphoneos;
474 | TARGETED_DEVICE_FAMILY = "1,2";
475 | };
476 | name = Debug;
477 | };
478 | 97C147041CF9000F007C117D /* Release */ = {
479 | isa = XCBuildConfiguration;
480 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
481 | buildSettings = {
482 | ALWAYS_SEARCH_USER_PATHS = NO;
483 | CLANG_ANALYZER_NONNULL = YES;
484 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
485 | CLANG_CXX_LIBRARY = "libc++";
486 | CLANG_ENABLE_MODULES = YES;
487 | CLANG_ENABLE_OBJC_ARC = YES;
488 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
489 | CLANG_WARN_BOOL_CONVERSION = YES;
490 | CLANG_WARN_COMMA = YES;
491 | CLANG_WARN_CONSTANT_CONVERSION = YES;
492 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
493 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
494 | CLANG_WARN_EMPTY_BODY = YES;
495 | CLANG_WARN_ENUM_CONVERSION = YES;
496 | CLANG_WARN_INFINITE_RECURSION = YES;
497 | CLANG_WARN_INT_CONVERSION = YES;
498 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
499 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
500 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
501 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
502 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
503 | CLANG_WARN_STRICT_PROTOTYPES = YES;
504 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
505 | CLANG_WARN_UNREACHABLE_CODE = YES;
506 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
507 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
508 | COPY_PHASE_STRIP = NO;
509 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
510 | ENABLE_NS_ASSERTIONS = NO;
511 | ENABLE_STRICT_OBJC_MSGSEND = YES;
512 | GCC_C_LANGUAGE_STANDARD = gnu99;
513 | GCC_NO_COMMON_BLOCKS = YES;
514 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
515 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
516 | GCC_WARN_UNDECLARED_SELECTOR = YES;
517 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
518 | GCC_WARN_UNUSED_FUNCTION = YES;
519 | GCC_WARN_UNUSED_VARIABLE = YES;
520 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
521 | MTL_ENABLE_DEBUG_INFO = NO;
522 | SDKROOT = iphoneos;
523 | SUPPORTED_PLATFORMS = iphoneos;
524 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
525 | TARGETED_DEVICE_FAMILY = "1,2";
526 | VALIDATE_PRODUCT = YES;
527 | };
528 | name = Release;
529 | };
530 | 97C147061CF9000F007C117D /* Debug */ = {
531 | isa = XCBuildConfiguration;
532 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
533 | buildSettings = {
534 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
535 | CLANG_ENABLE_MODULES = YES;
536 | CODE_SIGN_STYLE = Manual;
537 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
538 | DEVELOPMENT_TEAM = KEYR6LAUBK;
539 | ENABLE_BITCODE = NO;
540 | FRAMEWORK_SEARCH_PATHS = (
541 | "$(inherited)",
542 | "$(PROJECT_DIR)/Flutter",
543 | );
544 | INFOPLIST_FILE = Runner/Info.plist;
545 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
546 | LIBRARY_SEARCH_PATHS = (
547 | "$(inherited)",
548 | "$(PROJECT_DIR)/Flutter",
549 | );
550 | PRODUCT_BUNDLE_IDENTIFIER = com.xinthink.fltkeep;
551 | PRODUCT_NAME = Runner;
552 | PROVISIONING_PROFILE_SPECIFIER = "match Development com.xinthink.fltkeep";
553 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
554 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
555 | SWIFT_VERSION = 5.0;
556 | VERSIONING_SYSTEM = "apple-generic";
557 | };
558 | name = Debug;
559 | };
560 | 97C147071CF9000F007C117D /* Release */ = {
561 | isa = XCBuildConfiguration;
562 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
563 | buildSettings = {
564 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
565 | CLANG_ENABLE_MODULES = YES;
566 | CODE_SIGN_IDENTITY = "iPhone Distribution";
567 | CODE_SIGN_STYLE = Manual;
568 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
569 | DEVELOPMENT_TEAM = KEYR6LAUBK;
570 | ENABLE_BITCODE = NO;
571 | FRAMEWORK_SEARCH_PATHS = (
572 | "$(inherited)",
573 | "$(PROJECT_DIR)/Flutter",
574 | );
575 | INFOPLIST_FILE = Runner/Info.plist;
576 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
577 | LIBRARY_SEARCH_PATHS = (
578 | "$(inherited)",
579 | "$(PROJECT_DIR)/Flutter",
580 | );
581 | PRODUCT_BUNDLE_IDENTIFIER = com.xinthink.fltkeep;
582 | PRODUCT_NAME = Runner;
583 | PROVISIONING_PROFILE_SPECIFIER = "match AppStore com.xinthink.fltkeep";
584 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
585 | SWIFT_VERSION = 5.0;
586 | VERSIONING_SYSTEM = "apple-generic";
587 | };
588 | name = Release;
589 | };
590 | /* End XCBuildConfiguration section */
591 |
592 | /* Begin XCConfigurationList section */
593 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
594 | isa = XCConfigurationList;
595 | buildConfigurations = (
596 | 97C147031CF9000F007C117D /* Debug */,
597 | 97C147041CF9000F007C117D /* Release */,
598 | 249021D3217E4FDB00AE95B9 /* Profile */,
599 | );
600 | defaultConfigurationIsVisible = 0;
601 | defaultConfigurationName = Release;
602 | };
603 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
604 | isa = XCConfigurationList;
605 | buildConfigurations = (
606 | 97C147061CF9000F007C117D /* Debug */,
607 | 97C147071CF9000F007C117D /* Release */,
608 | 249021D4217E4FDB00AE95B9 /* Profile */,
609 | );
610 | defaultConfigurationIsVisible = 0;
611 | defaultConfigurationName = Release;
612 | };
613 | /* End XCConfigurationList section */
614 | };
615 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
616 | }
617 |
--------------------------------------------------------------------------------
/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 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/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/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xinthink/flutter-keep/2ee4e67ca8a4ac4c1fbdd6dc8bc406443ee90461/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | Flutter Keep
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/icons.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | /// Icon ideentifiers for the app.
4 | class AppIcons {
5 | AppIcons._();
6 |
7 | static const view_list = IconData(0xe900, fontFamily: 'app-icons');
8 | static const label = IconData(0xe901, fontFamily: 'app-icons');
9 | static const thumbtack = IconData(0xe902, fontFamily: 'app-icons');
10 | static const notifications = IconData(0xe903, fontFamily: 'app-icons');
11 | static const brush_sharp = IconData(0xe904, fontFamily: 'app-icons');
12 | static const mic = IconData(0xe905, fontFamily: 'app-icons');
13 | static const insert_photo = IconData(0xe906, fontFamily: 'app-icons');
14 | static const checkbox = IconData(0xe907, fontFamily: 'app-icons');
15 | static const view_grid = IconData(0xe908, fontFamily: 'app-icons');
16 | static const add_box = IconData(0xe909, fontFamily: 'app-icons');
17 | static const archive_outlined = IconData(0xe90a, fontFamily: 'app-icons');
18 | static const share_outlined = IconData(0xe90b, fontFamily: 'app-icons');
19 | static const notification_add = IconData(0xe90c, fontFamily: 'app-icons');
20 | static const pin_outlined = IconData(0xe90d, fontFamily: 'app-icons');
21 | static const pin = IconData(0xe90e, fontFamily: 'app-icons');
22 | static const copy = IconData(0xe90f, fontFamily: 'app-icons');
23 | static const delete_outline = IconData(0xe910, fontFamily: 'app-icons');
24 | static const settings_outlined = IconData(0xe911, fontFamily: 'app-icons');
25 | static const unarchive_outlined = IconData(0xe912, fontFamily: 'app-icons');
26 | }
27 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_core/firebase_core.dart' show Firebase;
2 | import 'package:firebase_auth/firebase_auth.dart' show FirebaseAuth;
3 | import 'package:flutter/material.dart';
4 | import 'package:provider/provider.dart';
5 |
6 | import 'models.dart' show CurrentUser;
7 | import 'screens.dart' show HomeScreen, LoginScreen, NoteEditor, SettingsScreen;
8 | import 'styles.dart';
9 |
10 | void main() async {
11 | WidgetsFlutterBinding.ensureInitialized();
12 | await Firebase.initializeApp();
13 | runApp(NotesApp());
14 | }
15 |
16 | class NotesApp extends StatelessWidget {
17 | // This widget is the root of your application.
18 | @override
19 | Widget build(BuildContext context) => StreamProvider.value(
20 | value: FirebaseAuth.instance.authStateChanges().map((user) => CurrentUser.create(user)),
21 | initialData: CurrentUser.initial,
22 | child: Consumer(
23 | builder: (context, user, _) => MaterialApp(
24 | title: 'Flutter Keep',
25 | theme: Theme.of(context).copyWith(
26 | brightness: Brightness.light,
27 | primaryColor: Colors.white,
28 | accentColor: kAccentColorLight,
29 | appBarTheme: AppBarTheme.of(context).copyWith(
30 | elevation: 0,
31 | brightness: Brightness.light,
32 | iconTheme: IconThemeData(
33 | color: kIconTintLight,
34 | ),
35 | ),
36 | scaffoldBackgroundColor: Colors.white,
37 | bottomAppBarColor: kBottomAppBarColorLight,
38 | primaryTextTheme: Theme.of(context).primaryTextTheme.copyWith(
39 | // title
40 | headline6: const TextStyle(
41 | color: kIconTintLight,
42 | ),
43 | ),
44 | ),
45 | home: user.isInitialValue
46 | ? Scaffold(body: const SizedBox())
47 | : user.data != null ? HomeScreen() : LoginScreen(),
48 | routes: {
49 | '/settings': (_) => SettingsScreen(),
50 | },
51 | onGenerateRoute: _generateRoute,
52 | ),
53 | ),
54 | );
55 |
56 | /// Handle named route
57 | Route _generateRoute(RouteSettings settings) {
58 | try {
59 | return _doGenerateRoute(settings);
60 | } catch (e, s) {
61 | debugPrint("failed to generate route for $settings: $e $s");
62 | return null;
63 | }
64 | }
65 |
66 | Route _doGenerateRoute(RouteSettings settings) {
67 | if (settings.name?.isNotEmpty != true) return null;
68 |
69 | final uri = Uri.parse(settings.name);
70 | final path = uri.path ?? '';
71 | // final q = uri.queryParameters ?? {};
72 | switch (path) {
73 | case '/note': {
74 | final note = (settings.arguments as Map ?? {})['note'];
75 | return _buildRoute(settings, (_) => NoteEditor(note: note));
76 | }
77 | default:
78 | return null;
79 | }
80 | }
81 |
82 | /// Create a [Route].
83 | Route _buildRoute(RouteSettings settings, WidgetBuilder builder) =>
84 | MaterialPageRoute(
85 | settings: settings,
86 | builder: builder,
87 | );
88 | }
89 |
--------------------------------------------------------------------------------
/lib/model/filter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 |
3 | import 'note.dart';
4 |
5 | /// Holds the current searching filter of notes.
6 | class NoteFilter extends ChangeNotifier {
7 | NoteState _noteState;
8 |
9 | /// The state of note to search.
10 | NoteState get noteState => _noteState;
11 | set noteState(NoteState value) {
12 | if (value != null && value != _noteState) {
13 | _noteState = value;
14 | notifyListeners();
15 | }
16 | }
17 |
18 | /// Creates a [NoteFilter] object.
19 | NoteFilter([this._noteState = NoteState.unspecified]);
20 | }
21 |
--------------------------------------------------------------------------------
/lib/model/note.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:cloud_firestore/cloud_firestore.dart';
4 | import 'package:flutter/foundation.dart';
5 | import 'package:intl/intl.dart';
6 |
7 | import 'package:flt_keep/services.dart' show NoteQuery;
8 |
9 | /// Data model of a note.
10 | class Note extends ChangeNotifier {
11 | final String id;
12 | String title;
13 | String content;
14 | Color color;
15 | NoteState state;
16 | final DateTime createdAt;
17 | DateTime modifiedAt;
18 |
19 | /// Instantiates a [Note].
20 | Note({
21 | this.id,
22 | this.title,
23 | this.content,
24 | this.color,
25 | this.state,
26 | DateTime createdAt,
27 | DateTime modifiedAt,
28 | }) : this.createdAt = createdAt ?? DateTime.now(),
29 | this.modifiedAt = modifiedAt ?? DateTime.now();
30 |
31 | /// Transforms the Firestore query [snapshot] into a list of [Note] instances.
32 | static List fromQuery(QuerySnapshot snapshot) => snapshot != null ? snapshot.toNotes() : [];
33 |
34 | /// Whether this note is pinned
35 | bool get pinned => state == NoteState.pinned;
36 |
37 | /// Returns an numeric form of the state
38 | int get stateValue => (state ?? NoteState.unspecified).index;
39 |
40 | bool get isNotEmpty => title?.isNotEmpty == true || content?.isNotEmpty == true;
41 |
42 | /// Formatted last modified time
43 | String get strLastModified => DateFormat.MMMd().format(modifiedAt);
44 |
45 | /// Update this note with another one.
46 | ///
47 | /// If [updateTimestamp] is `true`, which is the default,
48 | /// `modifiedAt` will be updated to `DateTime.now()`, otherwise, the value of `modifiedAt`
49 | /// will also be copied from [other].
50 | void update(Note other, {bool updateTimestamp = true}) {
51 | title = other.title;
52 | content = other.content;
53 | color = other.color;
54 | state = other.state;
55 |
56 | if (updateTimestamp || other.modifiedAt == null) {
57 | modifiedAt = DateTime.now();
58 | } else {
59 | modifiedAt = other.modifiedAt;
60 | }
61 | notifyListeners();
62 | }
63 |
64 | /// Update this note with specified properties.
65 | ///
66 | /// If [updateTimestamp] is `true`, which is the default,
67 | /// `modifiedAt` will be updated to `DateTime.now()`.
68 | Note updateWith({
69 | String title,
70 | String content,
71 | Color color,
72 | NoteState state,
73 | bool updateTimestamp = true,
74 | }) {
75 | if (title != null) this.title = title;
76 | if (content != null) this.content = content;
77 | if (color != null) this.color = color;
78 | if (state != null) this.state = state;
79 | if (updateTimestamp) modifiedAt = DateTime.now();
80 | notifyListeners();
81 | return this;
82 | }
83 |
84 | /// Serializes this note into a JSON object.
85 | Map toJson() => {
86 | 'title': title,
87 | 'content': content,
88 | 'color': color?.value,
89 | 'state': stateValue,
90 | 'createdAt': (createdAt ?? DateTime.now()).millisecondsSinceEpoch,
91 | 'modifiedAt': (modifiedAt ?? DateTime.now()).millisecondsSinceEpoch,
92 | };
93 |
94 | /// Make a copy of this note.
95 | ///
96 | /// If [updateTimestamp] is `true`, the defaults is `false`,
97 | /// timestamps both of `createdAt` & `modifiedAt` will be updated to `DateTime.now()`,
98 | /// or otherwise be identical with this note.
99 | Note copy({bool updateTimestamp = false}) => Note(
100 | id: id,
101 | createdAt: (updateTimestamp || createdAt == null) ? DateTime.now() : createdAt,
102 | )..update(this, updateTimestamp: updateTimestamp);
103 |
104 | @override
105 | bool operator ==(other) => other is Note &&
106 | (other.id ?? '') == (id ?? '') &&
107 | (other.title ?? '') == (title ?? '') &&
108 | (other.content ?? '') == (content ?? '') &&
109 | other.stateValue == stateValue &&
110 | (other.color ?? 0) == (color ?? 0);
111 |
112 | @override
113 | int get hashCode => id?.hashCode ?? super.hashCode;
114 | }
115 |
116 | /// State enum for a note.
117 | enum NoteState {
118 | unspecified,
119 | pinned,
120 | archived,
121 | deleted,
122 | }
123 |
124 | /// Add properties/methods to [NoteState]
125 | extension NoteStateX on NoteState {
126 | /// Checks if it's allowed to create a new note in this state.
127 | bool get canCreate => this <= NoteState.pinned;
128 |
129 | /// Checks if a note in this state can edit (modify / copy).
130 | bool get canEdit => this < NoteState.deleted;
131 |
132 | bool operator <(NoteState other) => (this?.index ?? 0) < (other?.index ?? 0);
133 | bool operator <=(NoteState other) => (this?.index ?? 0) <= (other?.index ?? 0);
134 |
135 | /// Message describes the state transition.
136 | String get message {
137 | switch (this) {
138 | case NoteState.archived:
139 | return 'Note archived';
140 | case NoteState.deleted:
141 | return 'Note moved to trash';
142 | default:
143 | return '';
144 | }
145 | }
146 |
147 | /// Label of the result-set filtered via this state.
148 | String get filterName {
149 | switch (this) {
150 | case NoteState.archived:
151 | return 'Archive';
152 | case NoteState.deleted:
153 | return 'Trash';
154 | default:
155 | return '';
156 | }
157 | }
158 |
159 | /// Short message explains an empty result-set filtered via this state.
160 | String get emptyResultMessage {
161 | switch (this) {
162 | case NoteState.archived:
163 | return 'Archived notes appear here';
164 | case NoteState.deleted:
165 | return 'Notes in trash appear here';
166 | default:
167 | return 'Notes you add appear here';
168 | }
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/lib/model/user.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_auth/firebase_auth.dart';
2 | import 'package:flutter/foundation.dart';
3 |
4 | /// A wrapper of [FirebaseUser] provides infomation to distinguish the initial value.
5 | @immutable
6 | class CurrentUser {
7 | final bool isInitialValue;
8 | final User data;
9 |
10 | const CurrentUser._(this.data, this.isInitialValue);
11 | factory CurrentUser.create(User data) => CurrentUser._(data, false);
12 |
13 | /// The inital empty instance.
14 | static const initial = CurrentUser._(null, true);
15 | }
16 |
--------------------------------------------------------------------------------
/lib/models.dart:
--------------------------------------------------------------------------------
1 | export 'model/filter.dart';
2 | export 'model/note.dart';
3 | export 'model/user.dart';
4 |
--------------------------------------------------------------------------------
/lib/screen/home_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:provider/provider.dart';
5 | import 'package:tuple/tuple.dart';
6 |
7 | import 'package:flt_keep/icons.dart' show AppIcons;
8 | import 'package:flt_keep/models.dart' show CurrentUser, Note, NoteState, NoteStateX, NoteFilter;
9 | import 'package:flt_keep/services.dart' show notesCollection, CommandHandler;
10 | import 'package:flt_keep/styles.dart';
11 | import 'package:flt_keep/utils.dart';
12 | import 'package:flt_keep/widgets.dart' show AppDrawer, NotesGrid, NotesList;
13 |
14 | /// Home screen, displays [Note] grid or list.
15 | class HomeScreen extends StatefulWidget {
16 | @override
17 | State createState() => _HomeScreenState();
18 | }
19 |
20 | /// [State] of [HomeScreen].
21 | class _HomeScreenState extends State with CommandHandler {
22 | final _scaffoldKey = GlobalKey();
23 |
24 | /// `true` to show notes in a GridView, a ListView otherwise.
25 | bool _gridView = true;
26 |
27 | @override
28 | Widget build(BuildContext context) => AnnotatedRegion(
29 | value: SystemUiOverlayStyle.dark.copyWith(
30 | // statusBarColor: Colors.white,
31 | systemNavigationBarColor: Colors.white,
32 | systemNavigationBarIconBrightness: Brightness.dark,
33 | ),
34 | child: MultiProvider(
35 | providers: [
36 | ChangeNotifierProvider(
37 | create: (_) => NoteFilter(), // watching the note filter
38 | ),
39 | Consumer(
40 | builder: (context, filter, child) => StreamProvider.value(
41 | value: _createNoteStream(context, filter), // applying the filter to Firestore query
42 | child: child,
43 | ),
44 | ),
45 | ],
46 | child: Consumer2>(
47 | builder: (context, filter, notes, child) {
48 | final hasNotes = notes?.isNotEmpty == true;
49 | final canCreate = filter.noteState.canCreate;
50 | return Scaffold(
51 | key: _scaffoldKey,
52 | body: Center(
53 | child: ConstrainedBox(
54 | constraints: const BoxConstraints.tightFor(width: 720),
55 | child: CustomScrollView(
56 | slivers: [
57 | _appBar(context, filter, child),
58 | if (hasNotes) const SliverToBoxAdapter(
59 | child: SizedBox(height: 24),
60 | ),
61 | ..._buildNotesView(context, filter, notes),
62 | if (hasNotes) SliverToBoxAdapter(
63 | child: SizedBox(height: (canCreate ? kBottomBarSize : 10.0) + 10.0),
64 | ),
65 | ],
66 | ),
67 | ),
68 | ),
69 | drawer: AppDrawer(),
70 | floatingActionButton: canCreate ? _fab(context) : null,
71 | bottomNavigationBar: canCreate ? _bottomActions() : null,
72 | floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
73 | extendBody: true,
74 | );
75 | },
76 | ),
77 | ),
78 | );
79 |
80 | Widget _appBar(BuildContext context, NoteFilter filter, Widget bottom) =>
81 | filter.noteState < NoteState.archived
82 | ? SliverAppBar(
83 | floating: true,
84 | snap: true,
85 | title: _topActions(context),
86 | automaticallyImplyLeading: false,
87 | centerTitle: true,
88 | titleSpacing: 0,
89 | backgroundColor: Colors.transparent,
90 | elevation: 0,
91 | )
92 | : SliverAppBar(
93 | floating: true,
94 | snap: true,
95 | title: Text(filter.noteState.filterName),
96 | leading: IconButton(
97 | icon: const Icon(Icons.menu),
98 | tooltip: 'Menu',
99 | onPressed: () => _scaffoldKey.currentState?.openDrawer(),
100 | ),
101 | automaticallyImplyLeading: false,
102 | );
103 |
104 | Widget _topActions(BuildContext context) => Container(
105 | // width: double.infinity,
106 | constraints: const BoxConstraints(
107 | maxWidth: 720,
108 | ),
109 | padding: const EdgeInsets.symmetric(horizontal: 20),
110 | child: Card(
111 | elevation: 2,
112 | child: Padding(
113 | padding: EdgeInsets.symmetric(vertical: isNotAndroid ? 7 : 5),
114 | child: Row(
115 | children: [
116 | const SizedBox(width: 20),
117 | InkWell(
118 | child: const Icon(Icons.menu),
119 | onTap: () => _scaffoldKey.currentState?.openDrawer(),
120 | ),
121 | const SizedBox(width: 16),
122 | const Expanded(
123 | child: Text('Search your notes',
124 | softWrap: false,
125 | style: TextStyle(
126 | color: kHintTextColorLight,
127 | fontSize: 16,
128 | ),
129 | ),
130 | ),
131 | const SizedBox(width: 16),
132 | InkWell(
133 | child: Icon(_gridView ? AppIcons.view_list : AppIcons.view_grid),
134 | onTap: () => setState(() {
135 | _gridView = !_gridView;
136 | }),
137 | ),
138 | const SizedBox(width: 18),
139 | _buildAvatar(context),
140 | const SizedBox(width: 10),
141 | ],
142 | ),
143 | ),
144 | ),
145 | );
146 |
147 | Widget _bottomActions() => BottomAppBar(
148 | shape: const CircularNotchedRectangle(),
149 | child: Container(
150 | height: kBottomBarSize,
151 | padding: const EdgeInsets.symmetric(horizontal: 17),
152 | child: Row(
153 | children: [
154 | const Icon(AppIcons.checkbox, size: 26, color: kIconTintLight),
155 | const SizedBox(width: 30),
156 | const Icon(AppIcons.brush_sharp, size: 26, color: kIconTintLight),
157 | const SizedBox(width: 30),
158 | const Icon(AppIcons.mic, size: 26, color: kIconTintLight),
159 | const SizedBox(width: 30),
160 | const Icon(AppIcons.insert_photo, size: 26, color: kIconTintLight),
161 | ],
162 | ),
163 | ),
164 | );
165 |
166 | Widget _fab(BuildContext context) => FloatingActionButton(
167 | backgroundColor: Theme.of(context).accentColor,
168 | child: const Icon(Icons.add),
169 | onPressed: () async {
170 | final command = await Navigator.pushNamed(context, '/note');
171 | debugPrint('--- noteEditor result: $command');
172 | processNoteCommand(_scaffoldKey.currentState, command);
173 | },
174 | );
175 |
176 | Widget _buildAvatar(BuildContext context) {
177 | final url = Provider.of(context)?.data?.photoURL;
178 | return CircleAvatar(
179 | backgroundImage: url != null ? NetworkImage(url) : null,
180 | child: url == null ? const Icon(Icons.face) : null,
181 | radius: isNotAndroid ? 19 : 17,
182 | );
183 | }
184 |
185 | /// A grid/list view to display notes
186 | ///
187 | /// Notes are divided to `Pinned` and `Others` when there's no filter,
188 | /// and a blank view will be rendered, if no note found.
189 | List _buildNotesView(BuildContext context, NoteFilter filter, List notes) {
190 | if (notes?.isNotEmpty != true) {
191 | return [_buildBlankView(filter.noteState)];
192 | }
193 |
194 | final asGrid = filter.noteState == NoteState.deleted || _gridView;
195 | final factory = asGrid ? NotesGrid.create : NotesList.create;
196 | final showPinned = filter.noteState == NoteState.unspecified;
197 |
198 | if (!showPinned) {
199 | return [
200 | factory(notes: notes, onTap: _onNoteTap),
201 | ];
202 | }
203 |
204 | final partition = _partitionNotes(notes);
205 | final hasPinned = partition.item1.isNotEmpty;
206 | final hasUnpinned = partition.item2.isNotEmpty;
207 |
208 | final _buildLabel = (String label, [double top = 26]) => SliverToBoxAdapter(
209 | child: Container(
210 | padding: EdgeInsetsDirectional.only(start: 26, bottom: 25, top: top),
211 | child: Text(label, style: const TextStyle(
212 | color: kHintTextColorLight,
213 | fontWeight: FontWeights.medium,
214 | fontSize: 12),
215 | ),
216 | ),
217 | );
218 |
219 | return [
220 | if (hasPinned) _buildLabel('PINNED', 0),
221 | if (hasPinned) factory(notes: partition.item1, onTap: _onNoteTap),
222 | if (hasPinned && hasUnpinned) _buildLabel('OTHERS'),
223 | factory(notes: partition.item2, onTap: _onNoteTap),
224 | ];
225 | }
226 |
227 | Widget _buildBlankView(NoteState filteredState) => SliverFillRemaining(
228 | hasScrollBody: false,
229 | child: Column(
230 | mainAxisAlignment: MainAxisAlignment.spaceAround,
231 | mainAxisSize: MainAxisSize.min,
232 | children: [
233 | const Expanded(flex: 1, child: SizedBox()),
234 | Icon(AppIcons.thumbtack,
235 | size: 120,
236 | color: kAccentColorLight.shade300,
237 | ),
238 | Expanded(
239 | flex: 2,
240 | child: Text(filteredState.emptyResultMessage,
241 | style: TextStyle(
242 | color: kHintTextColorLight,
243 | fontSize: 14,
244 | ),
245 | ),
246 | ),
247 | ],
248 | ),
249 | );
250 |
251 | /// Callback on a single note clicked
252 | void _onNoteTap(Note note) async {
253 | final command = await Navigator.pushNamed(context, '/note', arguments: { 'note': note });
254 | processNoteCommand(_scaffoldKey.currentState, command);
255 | }
256 |
257 | /// Create notes query
258 | Stream> _createNoteStream(BuildContext context, NoteFilter filter) {
259 | final user = Provider.of(context)?.data;
260 | final sinceSignUp = DateTime.now().millisecondsSinceEpoch -
261 | (user?.metadata?.creationTime?.millisecondsSinceEpoch ?? 0);
262 | final useIndexes = sinceSignUp >= _10_min_millis; // since creating indexes takes time, avoid using composite index until later
263 | final collection = notesCollection(user?.uid);
264 | final query = filter.noteState == NoteState.unspecified
265 | ? collection
266 | .where('state', isLessThan: NoteState.archived.index) // show both normal/pinned notes when no filter specified
267 | .orderBy('state', descending: true) // pinned notes come first
268 | : collection.where('state', isEqualTo: filter.noteState.index);
269 |
270 | return (useIndexes ? query.orderBy('createdAt', descending: true) : query)
271 | .snapshots()
272 | .handleError((e) => debugPrint('query notes failed: $e'))
273 | .map((snapshot) => Note.fromQuery(snapshot));
274 | }
275 |
276 | /// Partition the note list by the pinned state
277 | Tuple2, List> _partitionNotes(List notes) {
278 | if (notes?.isNotEmpty != true) {
279 | return Tuple2([], []);
280 | }
281 |
282 | final indexUnpinned = notes?.indexWhere((n) => !n.pinned);
283 | return indexUnpinned > -1
284 | ? Tuple2(notes.sublist(0, indexUnpinned), notes.sublist(indexUnpinned))
285 | : Tuple2(notes, []);
286 | }
287 | }
288 |
289 | const _10_min_millis = 600000;
290 |
--------------------------------------------------------------------------------
/lib/screen/login_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_auth/firebase_auth.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:google_sign_in/google_sign_in.dart' show GoogleSignIn;
5 |
6 | import 'package:flt_keep/styles.dart';
7 |
8 | /// Login screen.
9 | class LoginScreen extends StatefulWidget {
10 | @override
11 | State createState() => _LoginScreenState();
12 | }
13 |
14 | /// State for [LoginScreen].
15 | class _LoginScreenState extends State {
16 | final _auth = FirebaseAuth.instance;
17 | final _googleSignIn = GoogleSignIn();
18 |
19 | final _loginForm = GlobalKey();
20 | final _emailController = TextEditingController();
21 | final _passwordController = TextEditingController();
22 | bool _loggingIn = false;
23 | String _errorMessage;
24 | bool _useEmailSignIn = false;
25 |
26 | @override
27 | void dispose() {
28 | _emailController.dispose();
29 | _passwordController.dispose();
30 | super.dispose();
31 | }
32 |
33 | @override
34 | Widget build(BuildContext context) => Scaffold(
35 | body: Theme(
36 | data: ThemeData(primarySwatch: kAccentColorLight).copyWith(
37 | buttonTheme: ButtonTheme.of(context).copyWith(
38 | buttonColor: kAccentColorLight,
39 | textTheme: ButtonTextTheme.primary,
40 | ),
41 | ),
42 | child: Container(
43 | alignment: Alignment.topCenter,
44 | child: SingleChildScrollView(
45 | child: Container(
46 | constraints: const BoxConstraints(
47 | maxWidth: 560,
48 | ),
49 | padding: const EdgeInsets.symmetric(vertical: 100, horizontal: 48),
50 | child: Form(
51 | key: _loginForm,
52 | child: Column(
53 | children: [
54 | Image.asset('assets/images/thumbtack_intro.png'),
55 | const SizedBox(height: 32),
56 | const Text(
57 | 'Capture anything',
58 | style: TextStyle(
59 | fontSize: 24,
60 | fontWeight: FontWeights.medium,
61 | ),
62 | ),
63 | const SizedBox(height: 32),
64 | if (_useEmailSignIn) ..._buildEmailSignInFields(),
65 | if (!_useEmailSignIn) ..._buildGoogleSignInFields(),
66 | if (_errorMessage != null) _buildLoginMessage(),
67 | ],
68 | ),
69 | ),
70 | ),
71 | ),
72 | ),
73 | ),
74 | );
75 |
76 | List _buildGoogleSignInFields() => [
77 | RaisedButton(
78 | padding: const EdgeInsets.all(0),
79 | onPressed: _signInWithGoogle,
80 | child: Row(
81 | mainAxisSize: MainAxisSize.min,
82 | children: [
83 | Image.asset('assets/images/google.png', width: 40),
84 | Padding(
85 | padding: const EdgeInsets.symmetric(horizontal: 40 / 1.618),
86 | child: const Text('Continue with Google'),
87 | ),
88 | ],
89 | ),
90 | ),
91 | FlatButton(
92 | child: Text('Sign in with email'),
93 | onPressed: () => setState(() {
94 | _useEmailSignIn = true;
95 | }),
96 | ),
97 | if (_loggingIn)
98 | Container(
99 | width: 22,
100 | height: 22,
101 | margin: const EdgeInsets.only(top: 12),
102 | child: const CircularProgressIndicator(),
103 | ),
104 | ];
105 |
106 | List _buildEmailSignInFields() => [
107 | TextFormField(
108 | controller: _emailController,
109 | decoration: const InputDecoration(
110 | hintText: 'Email',
111 | ),
112 | validator: (value) => value.isEmpty ? 'Please input your email' : null,
113 | ),
114 | TextFormField(
115 | controller: _passwordController,
116 | decoration: const InputDecoration(
117 | hintText: 'Password',
118 | ),
119 | validator: (value) => value.isEmpty ? 'Please input your password' : null,
120 | obscureText: true,
121 | ),
122 | const SizedBox(height: 16),
123 | _buildEmailSignInButton(),
124 | if (_loggingIn) const LinearProgressIndicator(),
125 | FlatButton(
126 | child: Text('Use Google Sign In'),
127 | onPressed: () => setState(() {
128 | _useEmailSignIn = false;
129 | }),
130 | ),
131 | ];
132 |
133 | Widget _buildEmailSignInButton() => RaisedButton(
134 | onPressed: _signInWithEmail,
135 | child: Container(
136 | height: 40,
137 | alignment: Alignment.center,
138 | child: const Text('Sign in / Sign up'),
139 | ),
140 | );
141 |
142 | Widget _buildLoginMessage() => Container(
143 | alignment: Alignment.center,
144 | padding: const EdgeInsets.only(top: 18),
145 | child: Text(
146 | _errorMessage,
147 | style: const TextStyle(
148 | fontSize: 14,
149 | color: kErrorColorLight,
150 | ),
151 | ),
152 | );
153 |
154 | void _signInWithGoogle() async {
155 | _setLoggingIn();
156 | String errMsg;
157 |
158 | try {
159 | final googleUser = await _googleSignIn.signIn();
160 | final googleAuth = await googleUser.authentication;
161 | final credential = GoogleAuthProvider.credential(
162 | idToken: googleAuth.idToken,
163 | accessToken: googleAuth.accessToken,
164 | );
165 | await _auth.signInWithCredential(credential);
166 | } catch (e, s) {
167 | debugPrint('google signIn failed: $e. $s');
168 | errMsg = 'Login failed, please try again later.';
169 | } finally {
170 | _setLoggingIn(false, errMsg);
171 | }
172 | }
173 |
174 | void _signInWithEmail() async {
175 | if (_loginForm.currentState?.validate() != true) return;
176 |
177 | FocusScope.of(context).unfocus();
178 | String errMsg;
179 | try {
180 | _setLoggingIn();
181 | final result = await _doEmailSignIn(_emailController.text, _passwordController.text);
182 | debugPrint('Login result: $result');
183 | } on FirebaseAuthException catch (e) {
184 | errMsg = e.message;
185 | debugPrint('login failed: $errMsg [${e.code}].');
186 | } on PlatformException catch (e) {
187 | errMsg = e.message;
188 | debugPrint('login failed: $errMsg [${e.code}].');
189 | } catch (e, s) {
190 | debugPrint('login failed: $e. $s');
191 | errMsg = 'Login failed, please try again later.';
192 | } finally {
193 | _setLoggingIn(false, errMsg);
194 | }
195 | }
196 |
197 | Future _doEmailSignIn(
198 | String email,
199 | String password, {
200 | bool signUp = false,
201 | }) {
202 | final authFn = signUp ? _auth.createUserWithEmailAndPassword : _auth.signInWithEmailAndPassword;
203 | return authFn(email: email, password: password).catchError((e) {
204 | if (e is FirebaseAuthException && e.code == 'user-not-found') {
205 | return _doEmailSignIn(email, password, signUp: true);
206 | } else {
207 | throw e;
208 | }
209 | });
210 | }
211 |
212 | void _setLoggingIn([bool loggingIn = true, String errMsg]) {
213 | if (mounted) {
214 | setState(() {
215 | _loggingIn = loggingIn;
216 | _errorMessage = errMsg;
217 | });
218 | }
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/lib/screen/note_editor.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:provider/provider.dart';
6 |
7 | import 'package:flt_keep/icons.dart';
8 | import 'package:flt_keep/models.dart' show CurrentUser, Note, NoteState, NoteStateX;
9 | import 'package:flt_keep/services.dart';
10 | import 'package:flt_keep/styles.dart';
11 | import 'package:flt_keep/widgets.dart';
12 |
13 | /// The editor of a [Note], also shows every detail about a single note.
14 | class NoteEditor extends StatefulWidget {
15 | /// Create a [NoteEditor],
16 | /// provides an existed [note] in edit mode, or `null` to create a new one.
17 | const NoteEditor({Key key, this.note}) : super(key: key);
18 |
19 | final Note note;
20 |
21 | @override
22 | State createState() => _NoteEditorState(note);
23 | }
24 |
25 | /// [State] of [NoteEditor].
26 | class _NoteEditorState extends State with CommandHandler {
27 | /// Create a state for [NoteEditor], with an optional [note] being edited,
28 | /// otherwise a new one will be created.
29 | _NoteEditorState(Note note)
30 | : this._note = note ?? Note(),
31 | _originNote = note?.copy() ?? Note(),
32 | this._titleTextController = TextEditingController(text: note?.title),
33 | this._contentTextController = TextEditingController(text: note?.content);
34 |
35 | /// The note in editing
36 | final Note _note;
37 | /// The origin copy before editing
38 | final Note _originNote;
39 | Color get _noteColor => _note.color ?? kDefaultNoteColor;
40 |
41 | final _scaffoldKey = GlobalKey();
42 | StreamSubscription _noteSubscription;
43 | final TextEditingController _titleTextController;
44 | final TextEditingController _contentTextController;
45 |
46 | /// If the note is modified.
47 | bool get _isDirty => _note != _originNote;
48 |
49 | @override
50 | void initState() {
51 | super.initState();
52 | _titleTextController.addListener(() => _note.title = _titleTextController.text);
53 | _contentTextController.addListener(() => _note.content = _contentTextController.text);
54 | }
55 |
56 | @override
57 | void dispose() {
58 | _noteSubscription?.cancel();
59 | _titleTextController.dispose();
60 | _contentTextController.dispose();
61 | super.dispose();
62 | }
63 |
64 | @override
65 | Widget build(BuildContext context) {
66 | final uid = Provider.of(context).data.uid;
67 | _watchNoteDocument(uid);
68 | return ChangeNotifierProvider.value(
69 | value: _note,
70 | child: Consumer(
71 | builder: (_, __, ___) => Hero(
72 | tag: 'NoteItem${_note.id}',
73 | child: Theme(
74 | data: Theme.of(context).copyWith(
75 | primaryColor: _noteColor,
76 | appBarTheme: Theme.of(context).appBarTheme.copyWith(
77 | elevation: 0,
78 | ),
79 | scaffoldBackgroundColor: _noteColor,
80 | bottomAppBarColor: _noteColor,
81 | ),
82 | child: AnnotatedRegion(
83 | value: SystemUiOverlayStyle.dark.copyWith(
84 | statusBarColor: _noteColor,
85 | systemNavigationBarColor: _noteColor,
86 | systemNavigationBarIconBrightness: Brightness.dark,
87 | ),
88 | child: Scaffold(
89 | key: _scaffoldKey,
90 | appBar: AppBar(
91 | actions: _buildTopActions(context, uid),
92 | bottom: const PreferredSize(
93 | preferredSize: Size(0, 24),
94 | child: SizedBox(),
95 | ),
96 | ),
97 | body: _buildBody(context, uid),
98 | bottomNavigationBar: _buildBottomAppBar(context),
99 | ),
100 | ),
101 | ),
102 | ),
103 | ),
104 | );
105 | }
106 |
107 | Widget _buildBody(BuildContext context, String uid) => DefaultTextStyle(
108 | style: kNoteTextLargeLight,
109 | child: WillPopScope(
110 | onWillPop: () => _onPop(uid),
111 | child: Container(
112 | height: double.infinity,
113 | padding: const EdgeInsets.symmetric(horizontal: 24),
114 | child: SingleChildScrollView(
115 | child: _buildNoteDetail(),
116 | ),
117 | ),
118 | ),
119 | );
120 |
121 | Widget _buildNoteDetail() => Column(
122 | crossAxisAlignment: CrossAxisAlignment.stretch,
123 | children: [
124 | TextField(
125 | controller: _titleTextController,
126 | style: kNoteTitleLight,
127 | decoration: const InputDecoration(
128 | hintText: 'Title',
129 | border: InputBorder.none,
130 | counter: const SizedBox(),
131 | ),
132 | maxLines: null,
133 | maxLength: 1024,
134 | textCapitalization: TextCapitalization.sentences,
135 | readOnly: !_note.state.canEdit,
136 | ),
137 | const SizedBox(height: 14),
138 | TextField(
139 | controller: _contentTextController,
140 | style: kNoteTextLargeLight,
141 | decoration: const InputDecoration.collapsed(hintText: 'Note'),
142 | maxLines: null,
143 | textCapitalization: TextCapitalization.sentences,
144 | readOnly: !_note.state.canEdit,
145 | ),
146 | ],
147 | );
148 |
149 | List _buildTopActions(BuildContext context, String uid) => [
150 | if (_note.state != NoteState.deleted) IconButton(
151 | icon: Icon(_note.pinned == true ? AppIcons.pin : AppIcons.pin_outlined),
152 | tooltip: _note.pinned == true ? 'Unpin' : 'Pin',
153 | onPressed: () => _updateNoteState(uid, _note.pinned ? NoteState.unspecified : NoteState.pinned),
154 | ),
155 | if (_note.id != null && _note.state < NoteState.archived) IconButton(
156 | icon: const Icon(AppIcons.archive_outlined),
157 | tooltip: 'Archive',
158 | onPressed: () => Navigator.pop(context, NoteStateUpdateCommand(
159 | id: _note.id,
160 | uid: uid,
161 | from: _note.state,
162 | to: NoteState.archived,
163 | )),
164 | ),
165 | if (_note.state == NoteState.archived) IconButton(
166 | icon: const Icon(AppIcons.unarchive_outlined),
167 | tooltip: 'Unarchive',
168 | onPressed: () => _updateNoteState(uid, NoteState.unspecified),
169 | ),
170 | ];
171 |
172 | Widget _buildBottomAppBar(BuildContext context) => BottomAppBar(
173 | child: Container(
174 | height: kBottomBarSize,
175 | padding: const EdgeInsets.symmetric(horizontal: 9),
176 | child: Row(
177 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
178 | children: [
179 | IconButton(
180 | icon: const Icon(AppIcons.add_box),
181 | color: kIconTintLight,
182 | onPressed: _note.state.canEdit ? () {} : null,
183 | ),
184 | Text('Edited ${_note.strLastModified}'),
185 | IconButton(
186 | icon: const Icon(Icons.more_vert),
187 | color: kIconTintLight,
188 | onPressed: () => _showNoteBottomSheet(context),
189 | ),
190 | ],
191 | ),
192 | ),
193 | );
194 |
195 | void _showNoteBottomSheet(BuildContext context) async {
196 | final command = await showModalBottomSheet(
197 | context: context,
198 | backgroundColor: _noteColor,
199 | builder: (context) => ChangeNotifierProvider.value(
200 | value: _note,
201 | child: Consumer(
202 | builder: (_, note, __) => Container(
203 | color: note.color ?? kDefaultNoteColor,
204 | padding: const EdgeInsets.symmetric(vertical: 19),
205 | child: Column(
206 | mainAxisSize: MainAxisSize.min,
207 | children: [
208 | NoteActions(),
209 | if (_note.state.canEdit) const SizedBox(height: 16),
210 | if (_note.state.canEdit) LinearColorPicker(),
211 | const SizedBox(height: 12),
212 | ],
213 | ),
214 | ),
215 | ),
216 | ),
217 | );
218 |
219 | if (command != null) {
220 | if (command.dismiss) {
221 | Navigator.pop(context, command);
222 | } else {
223 | processNoteCommand(_scaffoldKey.currentState, command);
224 | }
225 | }
226 | }
227 |
228 | /// Callback before the user leave the editor.
229 | Future _onPop(String uid) {
230 | if (_isDirty && (_note.id != null || _note.isNotEmpty)) {
231 | _note
232 | ..modifiedAt = DateTime.now()
233 | ..saveToFireStore(uid);
234 | }
235 | return Future.value(true);
236 | }
237 |
238 | void _watchNoteDocument(String uid) {
239 | if (_noteSubscription == null && uid != null && _note.id != null) {
240 | _noteSubscription = noteDocument(_note.id, uid).snapshots()
241 | .map((snapshot) => snapshot.exists ? snapshot.toNote() : null)
242 | .listen(_onCloudNoteUpdated);
243 | }
244 | }
245 |
246 | /// Callback when the FireStore copy of this note updated.
247 | void _onCloudNoteUpdated(Note note) {
248 | if (!mounted || note?.isNotEmpty != true || _note == note) {
249 | return;
250 | }
251 |
252 | final refresh = () {
253 | _titleTextController.text = _note.title ?? '';
254 | _contentTextController.text = _note.content ?? '';
255 | _originNote.update(note, updateTimestamp: false);
256 | _note.update(note, updateTimestamp: false);
257 | };
258 |
259 | if (_isDirty) {
260 | _scaffoldKey.currentState?.showSnackBar(SnackBar(
261 | content: const Text('The note is updated on cloud.'),
262 | action: SnackBarAction(
263 | label: 'Refresh',
264 | onPressed: refresh,
265 | ),
266 | duration: const Duration(days: 1),
267 | ));
268 | } else {
269 | refresh();
270 | }
271 | }
272 |
273 | /// Update this note to the given [state]
274 | void _updateNoteState(uid, NoteState state) {
275 | // new note, update locally
276 | if (_note.id == null) {
277 | _note.updateWith(state: state);
278 | return;
279 | }
280 |
281 | // otherwise, handles it in a undoable manner
282 | processNoteCommand(_scaffoldKey.currentState, NoteStateUpdateCommand(
283 | id: _note.id,
284 | uid: uid,
285 | from: _note.state,
286 | to: state,
287 | ));
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/lib/screen/settings_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_auth/firebase_auth.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | import 'package:flt_keep/styles.dart';
5 |
6 | /// Settings screen.
7 | class SettingsScreen extends StatefulWidget {
8 | @override
9 | State createState() => _SettingsScreenState();
10 | }
11 |
12 | class _SettingsScreenState extends State {
13 | @override
14 | Widget build(BuildContext context) => Theme(
15 | data: Theme.of(context).copyWith(
16 | textTheme: Theme.of(context).textTheme.copyWith(
17 | caption: Theme.of(context).textTheme.caption.copyWith(
18 | color: Colors.blueAccent.shade400,
19 | fontWeight: FontWeights.medium,
20 | ),
21 | ),
22 | ),
23 | child: Builder(
24 | builder: (context) => Scaffold(
25 | appBar: AppBar(
26 | title: const Text('Settings'),
27 | ),
28 | body: SingleChildScrollView(
29 | child: Center(
30 | child: Container(
31 | constraints: const BoxConstraints.tightFor(width: 720),
32 | padding: const EdgeInsets.symmetric(vertical: 24),
33 | child: Column(
34 | crossAxisAlignment: CrossAxisAlignment.stretch,
35 | children: [
36 | _buildCaption(context, 'ACCOUNT'),
37 | ListTile(
38 | title: Text('Sign out'),
39 | onTap: () => _signOut(context),
40 | ),
41 | ],
42 | ),
43 | ),
44 | ),
45 | ),
46 | ),
47 | ),
48 | );
49 |
50 | Widget _buildCaption(BuildContext context, String title) => Padding(
51 | padding: const EdgeInsets.symmetric(horizontal: 16),
52 | child: Text('ACCOUNT', style: Theme.of(context).textTheme.caption),
53 | );
54 |
55 | void _signOut(BuildContext context) async {
56 | final yes = await showDialog(
57 | context: context,
58 | builder: (context) => AlertDialog(
59 | content: const Text('Are you sure to sign out the current account?'),
60 | actions: [
61 | FlatButton(
62 | child: const Text('No'),
63 | onPressed: () => Navigator.pop(context, false),
64 | ),
65 | FlatButton(
66 | child: const Text('Yes'),
67 | onPressed: () => Navigator.pop(context, true),
68 | ),
69 | ],
70 | ),
71 | );
72 |
73 | if (yes) {
74 | FirebaseAuth.instance.signOut();
75 | Navigator.pop(context);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/screens.dart:
--------------------------------------------------------------------------------
1 | export 'screen/home_screen.dart';
2 | export 'screen/login_screen.dart';
3 | export 'screen/note_editor.dart';
4 | export 'screen/settings_screen.dart';
5 |
--------------------------------------------------------------------------------
/lib/service/notes_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:collection_ext/iterables.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | import 'package:flt_keep/models.dart' show Note, NoteState;
6 | import 'package:flt_keep/styles.dart';
7 |
8 | /// An undoable action to a [Note].
9 | @immutable
10 | abstract class NoteCommand {
11 | final String id;
12 | final String uid;
13 |
14 | /// Whether this command should dismiss the current screen.
15 | final bool dismiss;
16 |
17 | /// Defines an undoable action to a note, provides the note [id], and current user [uid].
18 | const NoteCommand({
19 | @required this.id,
20 | @required this.uid,
21 | this.dismiss = false,
22 | });
23 |
24 | /// Returns `true` if this command is undoable.
25 | bool get isUndoable => true;
26 |
27 | /// Returns message about the result of the action.
28 | String get message => '';
29 |
30 | /// Executes this command.
31 | Future execute();
32 |
33 | /// Undo this command.
34 | Future revert();
35 | }
36 |
37 | /// A [NoteCommand] to update state of a [Note].
38 | class NoteStateUpdateCommand extends NoteCommand {
39 | final NoteState from;
40 | final NoteState to;
41 |
42 | /// Create a [NoteCommand] to update state of a note [from] the current state [to] another.
43 | NoteStateUpdateCommand({
44 | @required String id,
45 | @required String uid,
46 | @required this.from,
47 | @required this.to,
48 | bool dismiss = false,
49 | }) : super(id: id, uid: uid, dismiss: dismiss);
50 |
51 | @override
52 | String get message {
53 | switch (to) {
54 | case NoteState.deleted:
55 | return 'Note moved to trash';
56 | case NoteState.archived:
57 | return 'Note archived';
58 | case NoteState.pinned:
59 | return from == NoteState.archived
60 | ? 'Note pinned and unarchived' // pin an archived note
61 | : '';
62 | default:
63 | switch (from) {
64 | case NoteState.archived:
65 | return 'Note unarchived';
66 | case NoteState.deleted:
67 | return 'Note restored';
68 | default:
69 | return '';
70 | }
71 | }
72 | }
73 |
74 | @override
75 | Future execute() => updateNoteState(to, id, uid);
76 |
77 | @override
78 | Future revert() => updateNoteState(from, id, uid);
79 | }
80 |
81 | /// Mixin helps handle a [NoteCommand].
82 | mixin CommandHandler on State {
83 | /// Processes the given [command].
84 | Future processNoteCommand(ScaffoldState scaffoldState, NoteCommand command) async {
85 | if (command == null) {
86 | return;
87 | }
88 | await command.execute();
89 | final msg = command.message;
90 | if (mounted && msg?.isNotEmpty == true && command.isUndoable) {
91 | scaffoldState?.showSnackBar(SnackBar(
92 | content: Text(msg),
93 | action: SnackBarAction(
94 | label: 'Undo',
95 | onPressed: () => command.revert(),
96 | ),
97 | ));
98 | }
99 | }
100 | }
101 |
102 | /// Add note related methods to [QuerySnapshot].
103 | extension NoteQuery on QuerySnapshot {
104 | /// Transforms the query result into a list of notes.
105 | List toNotes() => docs
106 | .map((d) => d.toNote())
107 | .nonNull
108 | .asList();
109 | }
110 |
111 | /// Add note related methods to [QuerySnapshot].
112 | extension NoteDocument on DocumentSnapshot {
113 | /// Transforms the query result into a list of notes.
114 | Note toNote() {
115 | if (!exists) return null;
116 |
117 | final data = this.data();
118 | return Note(
119 | id: id,
120 | title: data['title'],
121 | content: data['content'],
122 | color: _parseColor(data['color']),
123 | state: NoteState.values[data['state'] ?? 0],
124 | createdAt: DateTime.fromMillisecondsSinceEpoch(data['createdAt'] ?? 0),
125 | modifiedAt: DateTime.fromMillisecondsSinceEpoch(data['modifiedAt'] ?? 0),
126 | );
127 | }
128 |
129 | Color _parseColor(num colorInt) => Color(colorInt ?? kNoteColors.first.value);
130 | }
131 |
132 | /// Add FireStore related methods to the [Note] model.
133 | extension NoteStore on Note {
134 | /// Save this note in FireStore.
135 | ///
136 | /// If this's a new note, a FireStore document will be created automatically.
137 | Future saveToFireStore(String uid) async {
138 | final col = notesCollection(uid);
139 | return id == null
140 | ? col.add(toJson())
141 | : col.doc(id).update(toJson());
142 | }
143 |
144 | /// Update this note to the given [state].
145 | Future updateState(NoteState state, String uid) async => id == null
146 | ? updateWith(state: state) // new note
147 | : updateNoteState(state, id, uid);
148 | }
149 |
150 | /// Returns reference to the notes collection of the user [uid].
151 | CollectionReference notesCollection(String uid) => FirebaseFirestore.instance.collection('notes-$uid');
152 |
153 | /// Returns reference to the given note [id] of the user [uid].
154 | DocumentReference noteDocument(String id, String uid) => notesCollection(uid).doc(id);
155 |
156 | /// Update a note to the [state], using information in the [command].
157 | Future updateNoteState(NoteState state, String id, String uid) =>
158 | updateNote({'state': state?.index ?? 0}, id, uid);
159 |
160 | /// Update a note [id] of user [uid] with properties [data].
161 | Future updateNote(Map data, String id, String uid) =>
162 | noteDocument(id, uid).update(data);
163 |
--------------------------------------------------------------------------------
/lib/services.dart:
--------------------------------------------------------------------------------
1 | export 'service/notes_service.dart';
2 |
--------------------------------------------------------------------------------
/lib/styles.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// font-weight definitions
4 | class FontWeights {
5 | FontWeights._();
6 |
7 | static const thin = FontWeight.w100;
8 | static const extraLight = FontWeight.w200;
9 | static const light = FontWeight.w300;
10 | static const normal = FontWeight.normal;
11 | static const medium = FontWeight.w500;
12 | static const semiBold = FontWeight.w600;
13 | static const bold = FontWeight.bold;
14 | static const extraBold = FontWeight.w800;
15 | static const black = FontWeight.w900;
16 | }
17 |
18 | const kBottomBarSize = 56.0;
19 | const kIconTintLight = Color(0xFF5F6368);
20 | const kIconTintCheckedLight = Color(0xFF202124);
21 | const kLabelColorLight = Color(0xFF202124);
22 | const kCheckedLabelBackgroudLight = Color(0x337E39FB);
23 | // const kCheckedLabelBackgroudLight = Color(0x3FFFD23E);
24 | const kHintTextColorLight = Color(0xFF61656A);
25 | const kNoteTitleColorLight = Color(0xFF202124);
26 | const kNoteTextColorLight = Color(0x99000000);
27 | const kNoteDetailTextColorLight = Color(0xC2000000);
28 | const kErrorColorLight = Color(0xFFD43131);
29 | const kWarnColorLight = Color(0xFFFD9726);
30 | const kBorderColorLight = Color(0xFFDADCE0);
31 | const kColorPickerBorderColor = Color(0x21000000);
32 | const kBottomAppBarColorLight = Color(0xF2FFFFFF);
33 |
34 | const _kPurplePrimaryValue = 0xFF7E39FB;
35 | const kAccentColorLight = MaterialColor(
36 | _kPurplePrimaryValue,
37 | {
38 | 900: Color(0xFF0000c9),
39 | 800: Color(0xFF3f00df),
40 | 700: Color(0xFF2500d7),
41 | 600: Color(0xFF6200ee),
42 | 500: Color(_kPurplePrimaryValue),
43 | 400: Color(0xFF5400e8),
44 | 300: Color(0xFF995dff),
45 | 200: Color(0xFFe3b8ff),
46 | 100: Color(0xFFdab2ff),
47 | 50: Color(0xFFfbd5ff),
48 | },
49 | );
50 |
51 | /// Available note background colors
52 | const Iterable kNoteColors = [
53 | Colors.white,
54 | Color(0xFFF28C82),
55 | Color(0xFFFABD03),
56 | Color(0xFFFFF476),
57 | Color(0xFFCDFF90),
58 | Color(0xFFA7FEEB),
59 | Color(0xFFCBF0F8),
60 | Color(0xFFAFCBFA),
61 | Color(0xFFD7AEFC),
62 | Color(0xFFFDCFE9),
63 | Color(0xFFE6C9A9),
64 | Color(0xFFE9EAEE),
65 | ];
66 | final kDefaultNoteColor = kNoteColors.first;
67 |
68 | /// [TextStyle] for note title in a preview card
69 | const kCardTitleLight = TextStyle(
70 | color: kNoteTitleColorLight,
71 | fontSize: 16,
72 | height: 19 / 16,
73 | fontWeight: FontWeights.medium,
74 | );
75 |
76 | /// [TextStyle] for note title in a preview card
77 | const kNoteTitleLight = TextStyle(
78 | color: kNoteTitleColorLight,
79 | fontSize: 21,
80 | height: 19 / 16,
81 | fontWeight: FontWeights.medium,
82 | );
83 |
84 | /// [TextStyle] for text notes
85 | const kNoteTextLight = TextStyle(
86 | color: kNoteTextColorLight,
87 | fontSize: 16,
88 | height: 1.3125,
89 | );
90 |
91 | /// [TextStyle] for text notes in detail view
92 | const kNoteTextLargeLight = TextStyle(
93 | color: kNoteDetailTextColorLight,
94 | fontSize: 18,
95 | height: 1.3125,
96 | );
97 |
98 | /// [TextStyle] for checklist notes
99 | const kChecklistTextLight = TextStyle(
100 | color: kNoteTextColorLight,
101 | fontSize: 14,
102 | height: 16 / 14,
103 | );
104 |
105 | /// [TextStyle] for checklist notes in detail view
106 | const kChecklistTextLargeLight = TextStyle(
107 | color: kNoteDetailTextColorLight,
108 | fontSize: 18,
109 | height: 16 / 14,
110 | );
111 |
--------------------------------------------------------------------------------
/lib/utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/foundation.dart';
4 |
5 | /// [Platform] is NOT supported on Web, make a workaround.
6 | bool get isNotIOS => kIsWeb || Platform.operatingSystem != 'ios';
7 |
8 | /// [Platform] is NOT supported on Web, make a workaround.
9 | bool get isNotAndroid => kIsWeb || Platform.operatingSystem != 'android';
10 |
--------------------------------------------------------------------------------
/lib/widget/color_picker.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection_ext/iterables.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:provider/provider.dart';
4 |
5 | import 'package:flt_keep/models.dart' show Note;
6 | import 'package:flt_keep/styles.dart';
7 |
8 | /// Note color picker in a horizontal list style.
9 | class LinearColorPicker extends StatelessWidget {
10 | /// Returns color of the note, fallbacks to the default color.
11 | Color _currColor(Note note) => note?.color ?? kDefaultNoteColor;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | Note note = Provider.of(context);
16 | return SingleChildScrollView(
17 | scrollDirection: Axis.horizontal,
18 | child: Row(
19 | children: kNoteColors.flatMapIndexed((i, color) => [
20 | if (i == 0) const SizedBox(width: 17),
21 | InkWell(
22 | child: Container(
23 | width: 36,
24 | height: 36,
25 | decoration: BoxDecoration(
26 | color: color,
27 | shape: BoxShape.circle,
28 | border: Border.all(color: kColorPickerBorderColor),
29 | ),
30 | child: color == _currColor(note) ? const Icon(Icons.check, color: kColorPickerBorderColor) : null,
31 | ),
32 | onTap: () {
33 | if (color != _currColor(note)) {
34 | note.updateWith(color: color);
35 | }
36 | },
37 | ),
38 | SizedBox(width: i == kNoteColors.length - 1 ? 17 : 20),
39 | ]).asList(),
40 | ),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/widget/drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:provider/provider.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:url_launcher/url_launcher.dart';
4 |
5 | import 'package:flt_keep/models.dart';
6 | import 'package:flt_keep/icons.dart';
7 | import 'package:flt_keep/styles.dart';
8 | import 'package:flt_keep/utils.dart';
9 |
10 | import 'drawer_filter.dart';
11 |
12 | /// Navigation drawer for the app.
13 | class AppDrawer extends StatelessWidget {
14 | @override
15 | Widget build(BuildContext context) => Consumer(
16 | builder: (context, filter, _) => Drawer(
17 | child: Column(
18 | crossAxisAlignment: CrossAxisAlignment.stretch,
19 | children: [
20 | _drawerHeader(context),
21 | if (isNotIOS) const SizedBox(height: 25),
22 | DrawerFilterItem(
23 | icon: AppIcons.thumbtack,
24 | title: 'Notes',
25 | isChecked: filter.noteState == NoteState.unspecified,
26 | onTap: () {
27 | filter.noteState = NoteState.unspecified;
28 | Navigator.pop(context);
29 | },
30 | ),
31 | // DrawerFilterItem(
32 | // icon: AppIcons.notifications,
33 | // title: 'Reminders',
34 | // ),
35 | const Divider(),
36 | DrawerFilterItem(
37 | icon: AppIcons.archive_outlined,
38 | title: 'Archive',
39 | isChecked: filter.noteState == NoteState.archived,
40 | onTap: () {
41 | filter.noteState = NoteState.archived;
42 | Navigator.pop(context);
43 | },
44 | ),
45 | DrawerFilterItem(
46 | icon: AppIcons.delete_outline,
47 | title: 'Trash',
48 | isChecked: filter.noteState == NoteState.deleted,
49 | onTap: () {
50 | filter.noteState = NoteState.deleted;
51 | Navigator.pop(context);
52 | },
53 | ),
54 | const Divider(),
55 | DrawerFilterItem(
56 | icon: AppIcons.settings_outlined,
57 | title: 'Settings',
58 | onTap: () {
59 | Navigator.popAndPushNamed(context, '/settings');
60 | },
61 | ),
62 | DrawerFilterItem(
63 | icon: Icons.help_outline,
64 | title: 'About',
65 | onTap: () => launch('https://github.com/xinthink/flutter-keep'),
66 | ),
67 | ],
68 | ),
69 | ),
70 | );
71 |
72 | Widget _drawerHeader(BuildContext context) => SafeArea(
73 | child: Container(
74 | padding: const EdgeInsets.only(top: 20, left: 30, right: 30),
75 | child: RichText(
76 | text: const TextSpan(
77 | style: TextStyle(
78 | color: kHintTextColorLight,
79 | fontSize: 26,
80 | fontWeight: FontWeights.light,
81 | letterSpacing: -2.5,
82 | ),
83 | children: [
84 | const TextSpan(
85 | text: 'Flt',
86 | style: TextStyle(
87 | color: kAccentColorLight,
88 | fontWeight: FontWeights.medium,
89 | fontStyle: FontStyle.italic,
90 | ),
91 | ),
92 | const TextSpan(text: ' Keep'),
93 | ],
94 | ),
95 | ),
96 | ),
97 | );
98 | }
99 |
--------------------------------------------------------------------------------
/lib/widget/drawer_filter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flt_keep/styles.dart';
3 |
4 | /// A single filter in the drawer's filters list.
5 | class DrawerFilterItem extends StatelessWidget {
6 | /// Build an instance of [DrawerFilterItem].
7 | ///
8 | /// Given a required [title], an optional leading [icon],
9 | /// and whether the item [isChecked].
10 | const DrawerFilterItem({
11 | Key key,
12 | this.icon,
13 | this.iconSize = 26,
14 | @required this.title,
15 | this.isChecked = false,
16 | this.onTap,
17 | }) : super(key: key);
18 |
19 | final IconData icon;
20 | final double iconSize;
21 | final String title;
22 | final bool isChecked;
23 | final VoidCallback onTap;
24 |
25 | @override
26 | Widget build(BuildContext context) => Padding(
27 | padding: const EdgeInsetsDirectional.only(end: 12),
28 | child: InkWell(
29 | borderRadius: const BorderRadius.horizontal(right: Radius.circular(30)),
30 | child: Container(
31 | decoration: ShapeDecoration(
32 | color: isChecked ? kCheckedLabelBackgroudLight : Colors.transparent,
33 | shape: RoundedRectangleBorder(
34 | borderRadius: BorderRadius.only(topRight: Radius.circular(30), bottomRight: Radius.circular(30)),
35 | ),
36 | ),
37 | padding: const EdgeInsetsDirectional.only(top: 12.5, bottom: 12.5, start: 30, end: 18),
38 | child: _content(),
39 | ),
40 | onTap: onTap,
41 | ),
42 | );
43 |
44 | Widget _content() => Row(
45 | children: [
46 | if (icon != null) Icon(icon,
47 | size: iconSize,
48 | color: isChecked ? kIconTintCheckedLight : kIconTintLight,
49 | ),
50 | if (icon != null) SizedBox(width: 24),
51 | Text(title,
52 | style: const TextStyle(
53 | color: kLabelColorLight,
54 | fontSize: 16,
55 | ),
56 | ),
57 | ],
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/lib/widget/note_actions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | import 'package:flt_keep/icons.dart';
5 | import 'package:flt_keep/models.dart';
6 | import 'package:flt_keep/services.dart';
7 | import 'package:flt_keep/styles.dart';
8 |
9 | /// Provide actions for a single [Note], used in a [BottomSheet].
10 | class NoteActions extends StatelessWidget {
11 | @override
12 | Widget build(BuildContext context) {
13 | final note = Provider.of(context);
14 | final state = note?.state;
15 | final id = note?.id;
16 | final uid = Provider.of(context)?.data?.uid;
17 |
18 | final textStyle = TextStyle(
19 | color: kHintTextColorLight,
20 | fontSize: 16,
21 | );
22 |
23 | return Column(
24 | mainAxisSize: MainAxisSize.min,
25 | children: [
26 | if (id != null && state < NoteState.archived) ListTile(
27 | leading: const Icon(AppIcons.archive_outlined),
28 | title: Text('Archive', style: textStyle),
29 | onTap: () => Navigator.pop(context, NoteStateUpdateCommand(
30 | id: id,
31 | uid: uid,
32 | from: state,
33 | to: NoteState.archived,
34 | dismiss: true,
35 | )),
36 | ),
37 | if (state == NoteState.archived) ListTile(
38 | leading: const Icon(AppIcons.unarchive_outlined),
39 | title: Text('Unarchive', style: textStyle),
40 | onTap: () => Navigator.pop(context, NoteStateUpdateCommand(
41 | id: id,
42 | uid: uid,
43 | from: state,
44 | to: NoteState.unspecified,
45 | )),
46 | ),
47 | if (id != null && state != NoteState.deleted) ListTile(
48 | leading: const Icon(AppIcons.delete_outline),
49 | title: Text('Delete', style: textStyle),
50 | onTap: () => Navigator.pop(context, NoteStateUpdateCommand(
51 | id: id,
52 | uid: uid,
53 | from: state,
54 | to: NoteState.deleted,
55 | dismiss: true,
56 | )),
57 | ),
58 | // if (id != null) ListTile(
59 | // leading: const Icon(AppIcons.copy),
60 | // title: Text('Make a copy', style: textStyle),
61 | // ),
62 | if (state == NoteState.deleted) ListTile(
63 | leading: const Icon(Icons.restore),
64 | title: Text('Restore', style: textStyle),
65 | onTap: () => Navigator.pop(context, NoteStateUpdateCommand(
66 | id: id,
67 | uid: uid,
68 | from: state,
69 | to: NoteState.unspecified,
70 | )),
71 | ),
72 | ListTile(
73 | leading: const Icon(AppIcons.share_outlined),
74 | title: Text('Send', style: textStyle),
75 | ),
76 | ],
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/lib/widget/note_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flt_keep/model/note.dart';
3 | import 'package:flt_keep/styles.dart';
4 |
5 | /// A single item (preview of a Note) in the Notes list.
6 | class NoteItem extends StatelessWidget {
7 | const NoteItem({
8 | Key key,
9 | this.note,
10 | }) : super(key: key);
11 |
12 | final Note note;
13 |
14 | @override
15 | Widget build(BuildContext context) => Hero(
16 | tag: 'NoteItem${note.id}',
17 | child: DefaultTextStyle(
18 | style: kNoteTextLight,
19 | child: Container(
20 | decoration: BoxDecoration(
21 | color: note.color,
22 | borderRadius: BorderRadius.all(Radius.circular(8)),
23 | border: note.color.value == 0xFFFFFFFF ? Border.all(color: kBorderColorLight) : null,
24 | ),
25 | padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16),
26 | child: Column(
27 | mainAxisSize: MainAxisSize.min,
28 | crossAxisAlignment: CrossAxisAlignment.stretch,
29 | children: [
30 | if (note.title?.isNotEmpty == true) Text(note.title,
31 | style: kCardTitleLight,
32 | maxLines: 1,
33 | ),
34 | if (note.title?.isNotEmpty == true) const SizedBox(height: 14),
35 | Flexible(
36 | flex: 1,
37 | child: Text(note.content ?? ''), // wrapping using a Flexible to avoid overflow
38 | ),
39 | ],
40 | ),
41 | ),
42 | ),
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/lib/widget/notes_grid.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:flt_keep/models.dart' show Note;
4 |
5 | import 'note_item.dart';
6 |
7 | /// Grid view of [Note]s.
8 | class NotesGrid extends StatelessWidget {
9 | final List notes;
10 | final void Function(Note) onTap;
11 |
12 | const NotesGrid({
13 | Key key,
14 | @required this.notes,
15 | this.onTap,
16 | }) : super(key: key);
17 |
18 | static NotesGrid create({
19 | Key key,
20 | @required List notes,
21 | void Function(Note) onTap,
22 | }) => NotesGrid(
23 | key: key,
24 | notes: notes,
25 | onTap: onTap,
26 | );
27 |
28 | @override
29 | Widget build(BuildContext context) => SliverPadding(
30 | padding: const EdgeInsets.symmetric(horizontal: 10),
31 | sliver: SliverGrid(
32 | gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
33 | maxCrossAxisExtent: 200.0,
34 | mainAxisSpacing: 10.0,
35 | crossAxisSpacing: 10.0,
36 | childAspectRatio: 1 / 1.2,
37 | ),
38 | delegate: SliverChildBuilderDelegate(
39 | (BuildContext context, int index) => _noteItem(context, notes[index]),
40 | childCount: notes.length,
41 | ),
42 | ),
43 | );
44 |
45 | Widget _noteItem(BuildContext context, Note note) => InkWell(
46 | onTap: () => onTap?.call(note),
47 | child: NoteItem(note: note),
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/lib/widget/notes_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection_ext/iterables.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flt_keep/model/note.dart';
4 |
5 | import 'note_item.dart';
6 |
7 | /// ListView for notes
8 | class NotesList extends StatelessWidget {
9 | final List notes;
10 | final void Function(Note) onTap;
11 |
12 | const NotesList({
13 | Key key,
14 | @required this.notes,
15 | this.onTap,
16 | }) : super(key: key);
17 |
18 | static NotesList create({
19 | Key key,
20 | @required List notes,
21 | void Function(Note) onTap,
22 | }) => NotesList(
23 | key: key,
24 | notes: notes,
25 | onTap: onTap,
26 | );
27 |
28 | @override
29 | Widget build(BuildContext context) => SliverPadding(
30 | padding: const EdgeInsets.symmetric(horizontal: 10),
31 | sliver: SliverList(
32 | delegate: SliverChildListDelegate(
33 | notes.flatMapIndexed((i, note) => [
34 | InkWell(
35 | onTap: () => onTap?.call(note),
36 | child: NoteItem(note: note),
37 | ),
38 | if (i < notes.length - 1) const SizedBox(height: 10),
39 | ]).asList(),
40 | ),
41 | ),
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/lib/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'widget/color_picker.dart';
2 | export 'widget/drawer.dart';
3 | export 'widget/note_actions.dart';
4 | export 'widget/note_item.dart';
5 | export 'widget/notes_grid.dart';
6 | export 'widget/notes_list.dart';
7 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "2.5.0"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.1.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "1.2.0"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.1.0"
39 | cloud_firestore:
40 | dependency: "direct main"
41 | description:
42 | name: cloud_firestore
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "0.16.0+1"
46 | cloud_firestore_platform_interface:
47 | dependency: transitive
48 | description:
49 | name: cloud_firestore_platform_interface
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "3.0.2"
53 | cloud_firestore_web:
54 | dependency: transitive
55 | description:
56 | name: cloud_firestore_web
57 | url: "https://pub.flutter-io.cn"
58 | source: hosted
59 | version: "0.3.0+2"
60 | collection:
61 | dependency: transitive
62 | description:
63 | name: collection
64 | url: "https://pub.flutter-io.cn"
65 | source: hosted
66 | version: "1.15.0"
67 | collection_ext:
68 | dependency: "direct main"
69 | description:
70 | name: collection_ext
71 | url: "https://pub.flutter-io.cn"
72 | source: hosted
73 | version: "0.2.0"
74 | cupertino_icons:
75 | dependency: "direct main"
76 | description:
77 | name: cupertino_icons
78 | url: "https://pub.flutter-io.cn"
79 | source: hosted
80 | version: "0.1.3"
81 | fake_async:
82 | dependency: transitive
83 | description:
84 | name: fake_async
85 | url: "https://pub.flutter-io.cn"
86 | source: hosted
87 | version: "1.2.0"
88 | firebase:
89 | dependency: transitive
90 | description:
91 | name: firebase
92 | url: "https://pub.flutter-io.cn"
93 | source: hosted
94 | version: "7.3.3"
95 | firebase_analytics:
96 | dependency: "direct main"
97 | description:
98 | name: firebase_analytics
99 | url: "https://pub.flutter-io.cn"
100 | source: hosted
101 | version: "7.0.1"
102 | firebase_analytics_platform_interface:
103 | dependency: transitive
104 | description:
105 | name: firebase_analytics_platform_interface
106 | url: "https://pub.flutter-io.cn"
107 | source: hosted
108 | version: "1.1.0"
109 | firebase_analytics_web:
110 | dependency: transitive
111 | description:
112 | name: firebase_analytics_web
113 | url: "https://pub.flutter-io.cn"
114 | source: hosted
115 | version: "0.1.1"
116 | firebase_auth:
117 | dependency: "direct main"
118 | description:
119 | name: firebase_auth
120 | url: "https://pub.flutter-io.cn"
121 | source: hosted
122 | version: "0.20.1"
123 | firebase_auth_platform_interface:
124 | dependency: transitive
125 | description:
126 | name: firebase_auth_platform_interface
127 | url: "https://pub.flutter-io.cn"
128 | source: hosted
129 | version: "3.1.0"
130 | firebase_auth_web:
131 | dependency: transitive
132 | description:
133 | name: firebase_auth_web
134 | url: "https://pub.flutter-io.cn"
135 | source: hosted
136 | version: "0.3.3"
137 | firebase_core:
138 | dependency: "direct main"
139 | description:
140 | name: firebase_core
141 | url: "https://pub.flutter-io.cn"
142 | source: hosted
143 | version: "0.7.0"
144 | firebase_core_platform_interface:
145 | dependency: transitive
146 | description:
147 | name: firebase_core_platform_interface
148 | url: "https://pub.flutter-io.cn"
149 | source: hosted
150 | version: "3.0.1"
151 | firebase_core_web:
152 | dependency: transitive
153 | description:
154 | name: firebase_core_web
155 | url: "https://pub.flutter-io.cn"
156 | source: hosted
157 | version: "0.2.1+3"
158 | flutter:
159 | dependency: "direct main"
160 | description: flutter
161 | source: sdk
162 | version: "0.0.0"
163 | flutter_test:
164 | dependency: "direct dev"
165 | description: flutter
166 | source: sdk
167 | version: "0.0.0"
168 | flutter_web_plugins:
169 | dependency: transitive
170 | description: flutter
171 | source: sdk
172 | version: "0.0.0"
173 | google_sign_in:
174 | dependency: "direct main"
175 | description:
176 | name: google_sign_in
177 | url: "https://pub.flutter-io.cn"
178 | source: hosted
179 | version: "4.5.9"
180 | google_sign_in_platform_interface:
181 | dependency: transitive
182 | description:
183 | name: google_sign_in_platform_interface
184 | url: "https://pub.flutter-io.cn"
185 | source: hosted
186 | version: "1.1.2"
187 | google_sign_in_web:
188 | dependency: transitive
189 | description:
190 | name: google_sign_in_web
191 | url: "https://pub.flutter-io.cn"
192 | source: hosted
193 | version: "0.9.2"
194 | http:
195 | dependency: transitive
196 | description:
197 | name: http
198 | url: "https://pub.flutter-io.cn"
199 | source: hosted
200 | version: "0.12.0+4"
201 | http_parser:
202 | dependency: transitive
203 | description:
204 | name: http_parser
205 | url: "https://pub.flutter-io.cn"
206 | source: hosted
207 | version: "3.1.3"
208 | intl:
209 | dependency: "direct main"
210 | description:
211 | name: intl
212 | url: "https://pub.flutter-io.cn"
213 | source: hosted
214 | version: "0.16.1"
215 | js:
216 | dependency: transitive
217 | description:
218 | name: js
219 | url: "https://pub.flutter-io.cn"
220 | source: hosted
221 | version: "0.6.3"
222 | matcher:
223 | dependency: transitive
224 | description:
225 | name: matcher
226 | url: "https://pub.flutter-io.cn"
227 | source: hosted
228 | version: "0.12.10"
229 | meta:
230 | dependency: transitive
231 | description:
232 | name: meta
233 | url: "https://pub.flutter-io.cn"
234 | source: hosted
235 | version: "1.3.0"
236 | nested:
237 | dependency: transitive
238 | description:
239 | name: nested
240 | url: "https://pub.flutter-io.cn"
241 | source: hosted
242 | version: "0.0.4"
243 | path:
244 | dependency: transitive
245 | description:
246 | name: path
247 | url: "https://pub.flutter-io.cn"
248 | source: hosted
249 | version: "1.8.0"
250 | pedantic:
251 | dependency: "direct dev"
252 | description:
253 | name: pedantic
254 | url: "https://pub.flutter-io.cn"
255 | source: hosted
256 | version: "1.8.0+1"
257 | plugin_platform_interface:
258 | dependency: transitive
259 | description:
260 | name: plugin_platform_interface
261 | url: "https://pub.flutter-io.cn"
262 | source: hosted
263 | version: "1.0.3"
264 | provider:
265 | dependency: "direct main"
266 | description:
267 | name: provider
268 | url: "https://pub.flutter-io.cn"
269 | source: hosted
270 | version: "4.0.2"
271 | quiver:
272 | dependency: transitive
273 | description:
274 | name: quiver
275 | url: "https://pub.flutter-io.cn"
276 | source: hosted
277 | version: "2.0.5"
278 | sky_engine:
279 | dependency: transitive
280 | description: flutter
281 | source: sdk
282 | version: "0.0.99"
283 | source_span:
284 | dependency: transitive
285 | description:
286 | name: source_span
287 | url: "https://pub.flutter-io.cn"
288 | source: hosted
289 | version: "1.8.0"
290 | stack_trace:
291 | dependency: transitive
292 | description:
293 | name: stack_trace
294 | url: "https://pub.flutter-io.cn"
295 | source: hosted
296 | version: "1.10.0"
297 | stream_channel:
298 | dependency: transitive
299 | description:
300 | name: stream_channel
301 | url: "https://pub.flutter-io.cn"
302 | source: hosted
303 | version: "2.1.0"
304 | string_scanner:
305 | dependency: transitive
306 | description:
307 | name: string_scanner
308 | url: "https://pub.flutter-io.cn"
309 | source: hosted
310 | version: "1.1.0"
311 | term_glyph:
312 | dependency: transitive
313 | description:
314 | name: term_glyph
315 | url: "https://pub.flutter-io.cn"
316 | source: hosted
317 | version: "1.2.0"
318 | test_api:
319 | dependency: transitive
320 | description:
321 | name: test_api
322 | url: "https://pub.flutter-io.cn"
323 | source: hosted
324 | version: "0.2.19"
325 | tuple:
326 | dependency: "direct main"
327 | description:
328 | name: tuple
329 | url: "https://pub.flutter-io.cn"
330 | source: hosted
331 | version: "1.0.3"
332 | typed_data:
333 | dependency: transitive
334 | description:
335 | name: typed_data
336 | url: "https://pub.flutter-io.cn"
337 | source: hosted
338 | version: "1.3.0"
339 | url_launcher:
340 | dependency: "direct main"
341 | description:
342 | name: url_launcher
343 | url: "https://pub.flutter-io.cn"
344 | source: hosted
345 | version: "5.4.2"
346 | url_launcher_macos:
347 | dependency: transitive
348 | description:
349 | name: url_launcher_macos
350 | url: "https://pub.flutter-io.cn"
351 | source: hosted
352 | version: "0.0.1+4"
353 | url_launcher_platform_interface:
354 | dependency: transitive
355 | description:
356 | name: url_launcher_platform_interface
357 | url: "https://pub.flutter-io.cn"
358 | source: hosted
359 | version: "1.0.6"
360 | url_launcher_web:
361 | dependency: transitive
362 | description:
363 | name: url_launcher_web
364 | url: "https://pub.flutter-io.cn"
365 | source: hosted
366 | version: "0.1.1+1"
367 | vector_math:
368 | dependency: transitive
369 | description:
370 | name: vector_math
371 | url: "https://pub.flutter-io.cn"
372 | source: hosted
373 | version: "2.1.0"
374 | sdks:
375 | dart: ">=2.12.0-0.0 <3.0.0"
376 | flutter: ">=1.12.13+hotfix.5"
377 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flt_keep
2 | description: A new Flutter project.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # In Android, build-name is used as versionName while build-number used as versionCode.
10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
12 | # Read more about iOS versioning at
13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14 | version: 1.1.0+2
15 |
16 | environment:
17 | sdk: ">=2.7.0 <3.0.0"
18 | flutter: ">=1.12.0"
19 |
20 | dependencies:
21 | flutter:
22 | sdk: flutter
23 |
24 | # The following adds the Cupertino Icons font to your application.
25 | # Use with the CupertinoIcons class for iOS style icons.
26 | cupertino_icons: ^0.1.2
27 |
28 | # Firebase
29 | firebase_core: ^0.7.0
30 | firebase_analytics: ^7.0.1
31 | firebase_auth: ^0.20.1
32 | cloud_firestore: ^0.16.0+1
33 | google_sign_in: ^4.5.9
34 |
35 | collection_ext: ^0.2.0
36 | provider: ^4.0.2
37 | tuple: ^1.0.3
38 | intl: ^0.16.1
39 | url_launcher: ^5.4.2
40 |
41 | dev_dependencies:
42 | flutter_test:
43 | sdk: flutter
44 | pedantic: ^1.8.0
45 |
46 |
47 | # For information on the generic Dart part of this file, see the
48 | # following page: https://dart.dev/tools/pub/pubspec
49 |
50 | # The following section is specific to Flutter.
51 | flutter:
52 |
53 | # The following line ensures that the Material Icons font is
54 | # included with your application, so that you can use the icons in
55 | # the material Icons class.
56 | uses-material-design: true
57 |
58 | # To add assets to your application, add an assets section, like this:
59 | assets:
60 | - assets/images/
61 |
62 | # To add custom fonts to your application, add a fonts section here,
63 | # For details regarding fonts from package dependencies,
64 | # see https://flutter.dev/custom-fonts/#from-packages
65 | fonts:
66 | - family: app-icons
67 | fonts:
68 | - asset: assets/fonts/app-icons.ttf
69 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:flt_keep/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(NotesApp());
17 | expect(find.byType(Scaffold), findsOneWidget);
18 |
19 | // // Verify that our counter starts at 0.
20 | // expect(find.text('0'), findsOneWidget);
21 | // expect(find.text('1'), findsNothing);
22 |
23 | // // Tap the '+' icon and trigger a frame.
24 | // await tester.tap(find.byIcon(Icons.add));
25 | // await tester.pump();
26 |
27 | // // Verify that our counter has incremented.
28 | // expect(find.text('0'), findsNothing);
29 | // expect(find.text('1'), findsOneWidget);
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/tool/arrange_images:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | PRJ=$(cd `dirname $0`/.. && pwd)
3 | MODULES=$PRJ/modules
4 |
5 | mvImg() {
6 | file=$1
7 | dest=$2
8 | echo "moving $file to $dest"
9 | if [ ! -d $dest ]; then
10 | mkdir -p $dest
11 | fi
12 | mv $file $dest
13 | }
14 |
15 | mvImgs() {
16 | fromDir=$1
17 | toDir=$2
18 |
19 | if [ -d $fromDir ]; then
20 | for f in $fromDir/* ; do
21 | if [ -f "$f" ]; then
22 | mvImg "$f" "$toDir"
23 | fi
24 | done
25 | fi
26 | }
27 |
28 | mvModuleImages() {
29 | moduleDir=$1
30 | imgs=$moduleDir/assets/images
31 | if [ -d $imgs ]; then
32 | cd $imgs
33 | echo "working in $(pwd)"
34 | mvImgs drawable-mdpi .
35 | mvImgs drawable-hdpi "1.5x"
36 | mvImgs drawable-xhdpi "2.0x"
37 | mvImgs drawable-xxhdpi "3.0x"
38 | mvImgs drawable-xxxhdpi "4.0x"
39 | echo "done"
40 | fi
41 | }
42 |
43 | mvModuleImages $PRJ
44 | for m in $MODULES/* ; do
45 | mvModuleImages "$m"
46 | done
47 |
--------------------------------------------------------------------------------
/tool/pkgs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | PRJ=$(cd `dirname $0`/.. && pwd)
3 | MODULES=$PRJ/modules
4 |
5 | # export PUB_HOSTED_URL=https://pub.flutter-io.cn
6 | # export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
7 |
8 | for m in $MODULES/* ; do
9 | pub=$m/pubspec.yaml
10 | # echo "check $m"
11 | if [ -f "$pub" ]; then
12 | echo "running flutter packages $@ in $m"
13 | cd $m && flutter packages "$@"
14 | fi
15 | done
16 |
17 | echo "running flutter packages $@ in $PRJ"
18 | cd $PRJ && flutter packages "$@"
19 |
--------------------------------------------------------------------------------
/web/config-firestore.js:
--------------------------------------------------------------------------------
1 |
2 | // Enable offline persistence
3 | firebase.firestore().enablePersistence()
4 | .catch(function(err) {
5 | console.log('enable firestore persistence failed', err);
6 | if (err.code == 'failed-precondition') {
7 | // Multiple tabs open, persistence can only be enabled
8 | // in one tab at a a time.
9 | // ...
10 | } else if (err.code == 'unimplemented') {
11 | // The current browser does not support all of the
12 | // features required to enable persistence
13 | // ...
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Flutter Keep
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------