├── LICENSE ├── README.md ├── firebase ├── .firebaserc ├── .gitignore ├── database.rules.json ├── firebase.json ├── firestore.indexes.json ├── firestore.rules ├── functions │ ├── .envrc │ ├── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── tslint.json ├── package-lock.json ├── public │ └── index.html └── storage.rules └── flutter ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── mono0926 │ │ │ │ └── google_tasks │ │ │ │ └── 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 └── settings.gradle ├── build.yaml ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Flutter.podspec │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.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 │ ├── GoogleService-Info.plist │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ └── ja.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── lib ├── app.dart ├── exception │ └── app_exception.dart ├── l10n │ ├── intl_en.arb │ ├── intl_ja.arb │ ├── intl_messages.arb │ ├── l10n.dart │ ├── l10n_delegate.dart │ ├── messages.dart │ ├── messages_all.dart │ ├── messages_en.dart │ ├── messages_ja.dart │ └── messages_messages.dart ├── main.dart ├── model │ ├── entity │ │ ├── entity.dart │ │ └── user │ │ │ ├── task │ │ │ ├── due.dart │ │ │ ├── due.freezed.dart │ │ │ ├── due.g.dart │ │ │ ├── task.dart │ │ │ ├── task.freezed.dart │ │ │ ├── task.g.dart │ │ │ └── task_doc.dart │ │ │ ├── user.dart │ │ │ ├── user.freezed.dart │ │ │ ├── user.g.dart │ │ │ └── user_doc.dart │ ├── model.dart │ ├── notifier │ │ ├── account_notifier.dart │ │ └── notifier.dart │ └── service │ │ ├── authenticator │ │ ├── auth_error_codes.dart │ │ └── authenticator.dart │ │ ├── service.dart │ │ ├── tasks_service.dart │ │ └── user_observer.dart ├── pages │ ├── root_page.dart │ ├── tasks_page │ │ ├── input_sheet │ │ │ ├── due_date_time_dialog.dart │ │ │ ├── input_sheet.dart │ │ │ └── model.dart │ │ ├── setting_sheet │ │ │ ├── model.dart │ │ │ └── setting_sheet.dart │ │ ├── task_detail_page │ │ │ └── task_detail_page.dart │ │ ├── task_tile │ │ │ ├── task_model.dart │ │ │ └── task_tile.dart │ │ ├── tasks_model.dart │ │ └── tasks_page.dart │ └── welcome_page │ │ ├── model.dart │ │ └── welcome_page.dart ├── router.dart ├── theme.dart ├── util │ ├── app_feedback.dart │ ├── app_navigator.dart │ ├── functions.dart │ ├── logger.dart │ ├── state_helper_mixin.dart │ └── util.dart └── widgets │ ├── due_button.dart │ ├── google_add.dart │ └── widgets.dart ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart └── update_l10n.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Masayuki Ono 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # google_tasks 2 | 3 | Google Tasks clone 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /firebase/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "gtasks-dev" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /firebase/.gitignore: -------------------------------------------------------------------------------- 1 | .history/ 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | firebase-debug.log* 10 | 11 | # Firebase cache 12 | .firebase/ 13 | 14 | # Firebase config 15 | 16 | # Uncomment this if you'd like others to create their own Firebase project. 17 | # For a team working on the same Firebase project(s), it is recommended to leave 18 | # it commented so all members can deploy to the same project(s) in .firebaserc. 19 | # .firebaserc 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | 33 | # nyc test coverage 34 | .nyc_output 35 | 36 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 37 | .grunt 38 | 39 | # Bower dependency directory (https://bower.io/) 40 | bower_components 41 | 42 | # node-waf configuration 43 | .lock-wscript 44 | 45 | # Compiled binary addons (http://nodejs.org/api/addons.html) 46 | build/Release 47 | 48 | # Dependency directories 49 | node_modules/ 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional REPL history 58 | .node_repl_history 59 | 60 | # Output of 'npm pack' 61 | *.tgz 62 | 63 | # Yarn Integrity file 64 | .yarn-integrity 65 | 66 | # dotenv environment variables file 67 | .env 68 | -------------------------------------------------------------------------------- /firebase/database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */ 3 | "rules": { 4 | ".read": false, 5 | ".write": false 6 | } 7 | } -------------------------------------------------------------------------------- /firebase/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "firestore": { 6 | "rules": "firestore.rules", 7 | "indexes": "firestore.indexes.json" 8 | }, 9 | "functions": { 10 | "predeploy": [ 11 | "npm --prefix \"$RESOURCE_DIR\" run lint", 12 | "npm --prefix \"$RESOURCE_DIR\" run build" 13 | ] 14 | }, 15 | "hosting": { 16 | "public": "public", 17 | "ignore": [ 18 | "firebase.json", 19 | "**/.*", 20 | "**/node_modules/**" 21 | ], 22 | "rewrites": [ 23 | { 24 | "source": "**", 25 | "destination": "/index.html" 26 | } 27 | ] 28 | }, 29 | "storage": { 30 | "rules": "storage.rules" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /firebase/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [], 3 | "fieldOverrides": [] 4 | } 5 | -------------------------------------------------------------------------------- /firebase/firestore.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | match /{document=**} { 5 | allow read, write: if false; 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /firebase/functions/.envrc: -------------------------------------------------------------------------------- 1 | export GOOGLE_APPLICATION_CREDENTIALS="./keys/firebase-key.json" 2 | -------------------------------------------------------------------------------- /firebase/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 | keys/ 11 | -------------------------------------------------------------------------------- /firebase/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 | "test": "mocha --timeout 5000 --reporter spec lib/test/**/*.js" 12 | }, 13 | "engines": { 14 | "node": "10" 15 | }, 16 | "main": "lib/index.js", 17 | "dependencies": { 18 | "firebase-admin": "^11.4.1", 19 | "firebase-functions": "^3.24.1" 20 | }, 21 | "devDependencies": { 22 | "@firebase/testing": "^0.11.9", 23 | "@types/chai": "^4.1.7", 24 | "@types/mocha": "^5.2.7", 25 | "chai": "^4.2.0", 26 | "firebase-functions-test": "^0.1.6", 27 | "mocha": "^6.2.0", 28 | "mocha-typescript": "^1.1.17", 29 | "tslint": "^5.18.0", 30 | "typescript": "^3.5.3" 31 | }, 32 | "private": true 33 | } 34 | -------------------------------------------------------------------------------- /firebase/functions/src/index.ts: -------------------------------------------------------------------------------- 1 | // import * as functions from 'firebase-functions'; 2 | 3 | // // Start writing Firebase Functions 4 | // // https://firebase.google.com/docs/functions/typescript 5 | // 6 | // export const helloWorld = functions.https.onRequest((request, response) => { 7 | // response.send("Hello from Firebase!"); 8 | // }); 9 | -------------------------------------------------------------------------------- /firebase/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 | "resolveJsonModule": true, 11 | "experimentalDecorators": true 12 | // "esModuleInterop": true 13 | }, 14 | "compileOnSave": true, 15 | "include": ["src"] 16 | } 17 | -------------------------------------------------------------------------------- /firebase/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, "dev"], 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 | // -- Strong Warnings -- 75 | // These rules should almost never be needed, but may be included due to legacy code. 76 | // They are left as a warning to avoid frustration with blocked deploys when the developer 77 | // understand the warning and wants to deploy anyway. 78 | 79 | // Warn when an empty interface is defined. These are generally not useful. 80 | "no-empty-interface": { "severity": "warning" }, 81 | 82 | // Warn when an import will have side effects. 83 | "no-import-side-effect": { "severity": "warning" }, 84 | 85 | // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for 86 | // most values and let for values that will change. 87 | "no-var-keyword": { "severity": "warning" }, 88 | 89 | // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental. 90 | "triple-equals": { "severity": "warning" }, 91 | 92 | // Warn when using deprecated APIs. 93 | "deprecation": { "severity": "warning" }, 94 | 95 | // -- Light Warnings -- 96 | // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info" 97 | // if TSLint supported such a level. 98 | 99 | // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array. 100 | // (Even better: check out utils like .map if transforming an array!) 101 | "prefer-for-of": { "severity": "warning" }, 102 | 103 | // Warns if function overloads could be unified into a single function with optional or rest parameters. 104 | "unified-signatures": { "severity": "warning" }, 105 | 106 | // Prefer const for values that will not change. This better documents code. 107 | "prefer-const": { "severity": "warning" }, 108 | 109 | // Multi-line object literals and function calls should have a trailing comma. This helps avoid merge conflicts. 110 | "trailing-comma": { "severity": "warning" }, 111 | "ordered-imports": [ 112 | true, 113 | { 114 | "grouped-imports": true 115 | } 116 | ] 117 | }, 118 | 119 | "defaultSeverity": "error" 120 | } 121 | -------------------------------------------------------------------------------- /firebase/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /firebase/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Welcome to Firebase Hosting 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 32 | 33 | 34 |
35 |

Welcome

36 |

Firebase Hosting Setup Complete

37 |

You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!

38 | Open Hosting Documentation 39 |
40 |

Firebase SDK Loading…

41 | 42 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /firebase/storage.rules: -------------------------------------------------------------------------------- 1 | service firebase.storage { 2 | match /b/{bucket}/o { 3 | match /{allPaths=**} { 4 | allow read, write: if request.auth!=null; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /flutter/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/Flutter/flutter_export_environment.sh 66 | **/ios/ServiceDefinitions.json 67 | **/ios/Runner/GeneratedPluginRegistrant.* 68 | 69 | # Exceptions to above rules. 70 | !**/ios/**/default.mode1v3 71 | !**/ios/**/default.mode2v3 72 | !**/ios/**/default.pbxuser 73 | !**/ios/**/default.perspectivev3 74 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 75 | -------------------------------------------------------------------------------- /flutter/.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: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /flutter/README.md: -------------------------------------------------------------------------------- 1 | # Google Tasks Clone 2 | 3 | [![Flutter Channel](https://img.shields.io/badge/Channel-dev-38B7F3.svg)](https://github.com/flutter/flutter/wiki/Flutter-build-release-channels) -------------------------------------------------------------------------------- /flutter/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # https://pub.dev/packages/pedantic_mono 2 | include: package:pedantic_mono/analysis_options.yaml 3 | 4 | analyzer: 5 | exclude: 6 | - lib/l10n/messages_*.dart 7 | - lib/**/*.g.dart 8 | - lib/**/*.freezed.dart 9 | - ios/** -------------------------------------------------------------------------------- /flutter/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.mono0926.gtasks.dev" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | multiDexEnabled true 48 | } 49 | 50 | buildTypes { 51 | release { 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.debug 55 | } 56 | } 57 | } 58 | 59 | flutter { 60 | source '../..' 61 | } 62 | 63 | dependencies { 64 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 65 | testImplementation 'junit:junit:4.12' 66 | androidTestImplementation 'androidx.test:runner:1.1.0' 67 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' 68 | } 69 | 70 | apply plugin: 'io.fabric' 71 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /flutter/android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "528887329462", 4 | "firebase_url": "https://gtasks-dev.firebaseio.com", 5 | "project_id": "gtasks-dev", 6 | "storage_bucket": "gtasks-dev.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:528887329462:android:a05769a60ff717bd", 12 | "android_client_info": { 13 | "package_name": "com.mono0926.gtasks.dev" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "528887329462-lesrk3vrmdg5um5lke0faekkhv79l2gb.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.mono0926.gtasks.dev", 22 | "certificate_hash": "9dd8ebf462b514a666fc15eb571ca66b3b9ab408" 23 | } 24 | }, 25 | { 26 | "client_id": "528887329462-ns4npqdh9bpnma2t5b3t25vu2jp0bdlt.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyD-Sc2pX2AL4xGJaDKqpRgJ6nH9ax8cGs4" 33 | } 34 | ], 35 | "services": { 36 | "appinvite_service": { 37 | "other_platform_oauth_client": [ 38 | { 39 | "client_id": "528887329462-ns4npqdh9bpnma2t5b3t25vu2jp0bdlt.apps.googleusercontent.com", 40 | "client_type": 3 41 | }, 42 | { 43 | "client_id": "528887329462-qet45ifecgpo6kgulhqcoqd1gsg0kqpo.apps.googleusercontent.com", 44 | "client_type": 2, 45 | "ios_info": { 46 | "bundle_id": "com.mono0926.gtasks.dev" 47 | } 48 | } 49 | ] 50 | } 51 | } 52 | } 53 | ], 54 | "configuration_version": "1" 55 | } -------------------------------------------------------------------------------- /flutter/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /flutter/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /flutter/android/app/src/main/kotlin/com/mono0926/google_tasks/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mono0926.google_tasks 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /flutter/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /flutter/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /flutter/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.10' 3 | repositories { 4 | google() 5 | jcenter() 6 | maven { 7 | url 'https://maven.fabric.io/public' 8 | } 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.5.0-rc02' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | classpath 'com.google.gms:google-services:4.3.0' 15 | classpath 'io.fabric.tools:gradle:1.26.1' 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | } 24 | } 25 | 26 | rootProject.buildDir = '../build' 27 | subprojects { 28 | project.buildDir = "${rootProject.buildDir}/${project.name}" 29 | } 30 | subprojects { 31 | project.evaluationDependsOn(':app') 32 | } 33 | 34 | task clean(type: Delete) { 35 | delete rootProject.buildDir 36 | } 37 | -------------------------------------------------------------------------------- /flutter/android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | org.gradle.jvmargs=-Xmx1536M 4 | 5 | -------------------------------------------------------------------------------- /flutter/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Aug 06 15:13:18 JST 2019 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.4.1-all.zip 7 | -------------------------------------------------------------------------------- /flutter/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 | -------------------------------------------------------------------------------- /flutter/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | any_map: true 7 | explicit_to_json: true 8 | -------------------------------------------------------------------------------- /flutter/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 | -------------------------------------------------------------------------------- /flutter/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /flutter/ios/Flutter/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'Flutter' 7 | s.version = '1.0.0' 8 | s.summary = 'High-performance, high-fidelity mobile apps.' 9 | s.description = <<-DESC 10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. 11 | DESC 12 | s.homepage = 'https://flutter.io' 13 | s.license = { :type => 'MIT' } 14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 16 | s.ios.deployment_target = '8.0' 17 | s.vendored_frameworks = 'Flutter.framework' 18 | end 19 | -------------------------------------------------------------------------------- /flutter/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /flutter/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 | post_install do |installer| 82 | installer.pods_project.targets.each do |target| 83 | target.build_configurations.each do |config| 84 | config.build_settings['ENABLE_BITCODE'] = 'NO' 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /flutter/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 | - cloud_functions (0.0.1): 179 | - Firebase/Core 180 | - Firebase/Functions (~> 6.0) 181 | - Flutter 182 | - cloud_functions_web (1.0.0): 183 | - Flutter 184 | - Crashlytics (3.14.0): 185 | - Fabric (~> 1.10.2) 186 | - Fabric (1.10.2) 187 | - Firebase/Analytics (6.21.0): 188 | - Firebase/Core 189 | - Firebase/Auth (6.21.0): 190 | - Firebase/CoreOnly 191 | - FirebaseAuth (~> 6.5.1) 192 | - Firebase/Core (6.21.0): 193 | - Firebase/CoreOnly 194 | - FirebaseAnalytics (= 6.4.0) 195 | - Firebase/CoreOnly (6.21.0): 196 | - FirebaseCore (= 6.6.5) 197 | - Firebase/Firestore (6.21.0): 198 | - Firebase/CoreOnly 199 | - FirebaseFirestore (~> 1.11.2) 200 | - Firebase/Functions (6.21.0): 201 | - Firebase/CoreOnly 202 | - FirebaseFunctions (~> 2.5.1) 203 | - Firebase/Performance (6.21.0): 204 | - Firebase/CoreOnly 205 | - FirebasePerformance (~> 3.1.11) 206 | - firebase_analytics (0.0.1): 207 | - Firebase/Analytics (~> 6.0) 208 | - Firebase/Core 209 | - Flutter 210 | - firebase_auth (0.0.1): 211 | - Firebase/Auth (~> 6.3) 212 | - Firebase/Core 213 | - Flutter 214 | - firebase_auth_web (0.1.0): 215 | - Flutter 216 | - firebase_core (0.0.1): 217 | - Firebase/Core 218 | - Flutter 219 | - firebase_core_web (0.1.0): 220 | - Flutter 221 | - firebase_crashlytics (0.0.1): 222 | - Crashlytics 223 | - Fabric 224 | - Firebase/Core 225 | - Flutter 226 | - firebase_performance (0.0.1): 227 | - Firebase/Core 228 | - Firebase/Performance 229 | - Flutter 230 | - FirebaseABTesting (3.2.0): 231 | - FirebaseAnalyticsInterop (~> 1.3) 232 | - FirebaseCore (~> 6.1) 233 | - Protobuf (>= 3.9.2, ~> 3.9) 234 | - FirebaseAnalytics (6.4.0): 235 | - FirebaseCore (~> 6.6) 236 | - FirebaseInstallations (~> 1.1) 237 | - GoogleAppMeasurement (= 6.4.0) 238 | - GoogleUtilities/AppDelegateSwizzler (~> 6.0) 239 | - GoogleUtilities/MethodSwizzler (~> 6.0) 240 | - GoogleUtilities/Network (~> 6.0) 241 | - "GoogleUtilities/NSData+zlib (~> 6.0)" 242 | - nanopb (= 0.3.9011) 243 | - FirebaseAnalyticsInterop (1.5.0) 244 | - FirebaseAuth (6.5.1): 245 | - FirebaseAuthInterop (~> 1.0) 246 | - FirebaseCore (~> 6.6) 247 | - GoogleUtilities/AppDelegateSwizzler (~> 6.5) 248 | - GoogleUtilities/Environment (~> 6.5) 249 | - GTMSessionFetcher/Core (~> 1.1) 250 | - FirebaseAuthInterop (1.1.0) 251 | - FirebaseCore (6.6.5): 252 | - FirebaseCoreDiagnostics (~> 1.2) 253 | - FirebaseCoreDiagnosticsInterop (~> 1.2) 254 | - GoogleUtilities/Environment (~> 6.5) 255 | - GoogleUtilities/Logger (~> 6.5) 256 | - FirebaseCoreDiagnostics (1.2.2): 257 | - FirebaseCoreDiagnosticsInterop (~> 1.2) 258 | - GoogleDataTransportCCTSupport (~> 2.0) 259 | - GoogleUtilities/Environment (~> 6.5) 260 | - GoogleUtilities/Logger (~> 6.5) 261 | - nanopb (~> 0.3.901) 262 | - FirebaseCoreDiagnosticsInterop (1.2.0) 263 | - FirebaseFirestore (1.11.2): 264 | - abseil/algorithm (= 0.20190808) 265 | - abseil/base (= 0.20190808) 266 | - abseil/memory (= 0.20190808) 267 | - abseil/meta (= 0.20190808) 268 | - abseil/strings/strings (= 0.20190808) 269 | - abseil/time (= 0.20190808) 270 | - abseil/types (= 0.20190808) 271 | - FirebaseAuthInterop (~> 1.0) 272 | - FirebaseCore (~> 6.2) 273 | - "gRPC-C++ (= 0.0.9)" 274 | - leveldb-library (~> 1.22) 275 | - nanopb (~> 0.3.901) 276 | - FirebaseFunctions (2.5.1): 277 | - FirebaseAuthInterop (~> 1.0) 278 | - FirebaseCore (~> 6.0) 279 | - GTMSessionFetcher/Core (~> 1.1) 280 | - FirebaseInstallations (1.1.1): 281 | - FirebaseCore (~> 6.6) 282 | - GoogleUtilities/UserDefaults (~> 6.5) 283 | - PromisesObjC (~> 1.2) 284 | - FirebaseInstanceID (4.3.2): 285 | - FirebaseCore (~> 6.6) 286 | - FirebaseInstallations (~> 1.0) 287 | - GoogleUtilities/Environment (~> 6.5) 288 | - GoogleUtilities/UserDefaults (~> 6.5) 289 | - FirebasePerformance (3.1.11): 290 | - FirebaseCore (~> 6.6) 291 | - FirebaseInstallations (~> 1.1) 292 | - FirebaseRemoteConfig (~> 4.4) 293 | - GoogleToolboxForMac/Logger (~> 2.1) 294 | - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" 295 | - GoogleUtilities/Environment (~> 6.2) 296 | - GoogleUtilities/ISASwizzler (~> 6.2) 297 | - GoogleUtilities/MethodSwizzler (~> 6.2) 298 | - GTMSessionFetcher/Core (~> 1.1) 299 | - Protobuf (~> 3.9) 300 | - FirebaseRemoteConfig (4.4.9): 301 | - FirebaseABTesting (~> 3.1) 302 | - FirebaseAnalyticsInterop (~> 1.4) 303 | - FirebaseCore (~> 6.2) 304 | - FirebaseInstanceID (~> 4.2) 305 | - GoogleUtilities/Environment (~> 6.2) 306 | - "GoogleUtilities/NSData+zlib (~> 6.2)" 307 | - Protobuf (>= 3.9.2, ~> 3.9) 308 | - Flutter (1.0.0) 309 | - FMDB (2.7.5): 310 | - FMDB/standard (= 2.7.5) 311 | - FMDB/standard (2.7.5) 312 | - google_sign_in (0.0.1): 313 | - Flutter 314 | - GoogleSignIn (~> 5.0) 315 | - google_sign_in_web (0.8.1): 316 | - Flutter 317 | - GoogleAppMeasurement (6.4.0): 318 | - GoogleUtilities/AppDelegateSwizzler (~> 6.0) 319 | - GoogleUtilities/MethodSwizzler (~> 6.0) 320 | - GoogleUtilities/Network (~> 6.0) 321 | - "GoogleUtilities/NSData+zlib (~> 6.0)" 322 | - nanopb (= 0.3.9011) 323 | - GoogleDataTransport (5.1.0) 324 | - GoogleDataTransportCCTSupport (2.0.1): 325 | - GoogleDataTransport (~> 5.1) 326 | - nanopb (~> 0.3.901) 327 | - GoogleSignIn (5.0.2): 328 | - AppAuth (~> 1.2) 329 | - GTMAppAuth (~> 1.0) 330 | - GTMSessionFetcher/Core (~> 1.1) 331 | - GoogleToolboxForMac/Defines (2.2.2) 332 | - GoogleToolboxForMac/Logger (2.2.2): 333 | - GoogleToolboxForMac/Defines (= 2.2.2) 334 | - "GoogleToolboxForMac/NSData+zlib (2.2.2)": 335 | - GoogleToolboxForMac/Defines (= 2.2.2) 336 | - GoogleUtilities/AppDelegateSwizzler (6.5.2): 337 | - GoogleUtilities/Environment 338 | - GoogleUtilities/Logger 339 | - GoogleUtilities/Network 340 | - GoogleUtilities/Environment (6.5.2) 341 | - GoogleUtilities/ISASwizzler (6.5.2) 342 | - GoogleUtilities/Logger (6.5.2): 343 | - GoogleUtilities/Environment 344 | - GoogleUtilities/MethodSwizzler (6.5.2): 345 | - GoogleUtilities/Logger 346 | - GoogleUtilities/Network (6.5.2): 347 | - GoogleUtilities/Logger 348 | - "GoogleUtilities/NSData+zlib" 349 | - GoogleUtilities/Reachability 350 | - "GoogleUtilities/NSData+zlib (6.5.2)" 351 | - GoogleUtilities/Reachability (6.5.2): 352 | - GoogleUtilities/Logger 353 | - GoogleUtilities/UserDefaults (6.5.2): 354 | - GoogleUtilities/Logger 355 | - "gRPC-C++ (0.0.9)": 356 | - "gRPC-C++/Implementation (= 0.0.9)" 357 | - "gRPC-C++/Interface (= 0.0.9)" 358 | - "gRPC-C++/Implementation (0.0.9)": 359 | - "gRPC-C++/Interface (= 0.0.9)" 360 | - gRPC-Core (= 1.21.0) 361 | - nanopb (~> 0.3) 362 | - "gRPC-C++/Interface (0.0.9)" 363 | - gRPC-Core (1.21.0): 364 | - gRPC-Core/Implementation (= 1.21.0) 365 | - gRPC-Core/Interface (= 1.21.0) 366 | - gRPC-Core/Implementation (1.21.0): 367 | - BoringSSL-GRPC (= 0.0.3) 368 | - gRPC-Core/Interface (= 1.21.0) 369 | - nanopb (~> 0.3) 370 | - gRPC-Core/Interface (1.21.0) 371 | - GTMAppAuth (1.0.0): 372 | - AppAuth/Core (~> 1.0) 373 | - GTMSessionFetcher (~> 1.1) 374 | - GTMSessionFetcher (1.3.1): 375 | - GTMSessionFetcher/Full (= 1.3.1) 376 | - GTMSessionFetcher/Core (1.3.1) 377 | - GTMSessionFetcher/Full (1.3.1): 378 | - GTMSessionFetcher/Core (= 1.3.1) 379 | - leveldb-library (1.22) 380 | - mono_kit (0.0.1): 381 | - Flutter 382 | - nanopb (0.3.9011): 383 | - nanopb/decode (= 0.3.9011) 384 | - nanopb/encode (= 0.3.9011) 385 | - nanopb/decode (0.3.9011) 386 | - nanopb/encode (0.3.9011) 387 | - path_provider (0.0.1): 388 | - Flutter 389 | - path_provider_macos (0.0.1): 390 | - Flutter 391 | - PromisesObjC (1.2.8) 392 | - Protobuf (3.11.4) 393 | - sqflite (0.0.1): 394 | - Flutter 395 | - FMDB (~> 2.7.2) 396 | - url_launcher (0.0.1): 397 | - Flutter 398 | - url_launcher_macos (0.0.1): 399 | - Flutter 400 | - url_launcher_web (0.0.1): 401 | - Flutter 402 | 403 | DEPENDENCIES: 404 | - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) 405 | - cloud_firestore_web (from `.symlinks/plugins/cloud_firestore_web/ios`) 406 | - cloud_functions (from `.symlinks/plugins/cloud_functions/ios`) 407 | - cloud_functions_web (from `.symlinks/plugins/cloud_functions_web/ios`) 408 | - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) 409 | - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) 410 | - firebase_auth_web (from `.symlinks/plugins/firebase_auth_web/ios`) 411 | - firebase_core (from `.symlinks/plugins/firebase_core/ios`) 412 | - firebase_core_web (from `.symlinks/plugins/firebase_core_web/ios`) 413 | - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) 414 | - firebase_performance (from `.symlinks/plugins/firebase_performance/ios`) 415 | - Flutter (from `Flutter`) 416 | - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`) 417 | - google_sign_in_web (from `.symlinks/plugins/google_sign_in_web/ios`) 418 | - mono_kit (from `.symlinks/plugins/mono_kit/ios`) 419 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 420 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 421 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 422 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 423 | - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) 424 | - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) 425 | 426 | SPEC REPOS: 427 | trunk: 428 | - abseil 429 | - AppAuth 430 | - BoringSSL-GRPC 431 | - Crashlytics 432 | - Fabric 433 | - Firebase 434 | - FirebaseABTesting 435 | - FirebaseAnalytics 436 | - FirebaseAnalyticsInterop 437 | - FirebaseAuth 438 | - FirebaseAuthInterop 439 | - FirebaseCore 440 | - FirebaseCoreDiagnostics 441 | - FirebaseCoreDiagnosticsInterop 442 | - FirebaseFirestore 443 | - FirebaseFunctions 444 | - FirebaseInstallations 445 | - FirebaseInstanceID 446 | - FirebasePerformance 447 | - FirebaseRemoteConfig 448 | - FMDB 449 | - GoogleAppMeasurement 450 | - GoogleDataTransport 451 | - GoogleDataTransportCCTSupport 452 | - GoogleSignIn 453 | - GoogleToolboxForMac 454 | - GoogleUtilities 455 | - "gRPC-C++" 456 | - gRPC-Core 457 | - GTMAppAuth 458 | - GTMSessionFetcher 459 | - leveldb-library 460 | - nanopb 461 | - PromisesObjC 462 | - Protobuf 463 | 464 | EXTERNAL SOURCES: 465 | cloud_firestore: 466 | :path: ".symlinks/plugins/cloud_firestore/ios" 467 | cloud_firestore_web: 468 | :path: ".symlinks/plugins/cloud_firestore_web/ios" 469 | cloud_functions: 470 | :path: ".symlinks/plugins/cloud_functions/ios" 471 | cloud_functions_web: 472 | :path: ".symlinks/plugins/cloud_functions_web/ios" 473 | firebase_analytics: 474 | :path: ".symlinks/plugins/firebase_analytics/ios" 475 | firebase_auth: 476 | :path: ".symlinks/plugins/firebase_auth/ios" 477 | firebase_auth_web: 478 | :path: ".symlinks/plugins/firebase_auth_web/ios" 479 | firebase_core: 480 | :path: ".symlinks/plugins/firebase_core/ios" 481 | firebase_core_web: 482 | :path: ".symlinks/plugins/firebase_core_web/ios" 483 | firebase_crashlytics: 484 | :path: ".symlinks/plugins/firebase_crashlytics/ios" 485 | firebase_performance: 486 | :path: ".symlinks/plugins/firebase_performance/ios" 487 | Flutter: 488 | :path: Flutter 489 | google_sign_in: 490 | :path: ".symlinks/plugins/google_sign_in/ios" 491 | google_sign_in_web: 492 | :path: ".symlinks/plugins/google_sign_in_web/ios" 493 | mono_kit: 494 | :path: ".symlinks/plugins/mono_kit/ios" 495 | path_provider: 496 | :path: ".symlinks/plugins/path_provider/ios" 497 | path_provider_macos: 498 | :path: ".symlinks/plugins/path_provider_macos/ios" 499 | sqflite: 500 | :path: ".symlinks/plugins/sqflite/ios" 501 | url_launcher: 502 | :path: ".symlinks/plugins/url_launcher/ios" 503 | url_launcher_macos: 504 | :path: ".symlinks/plugins/url_launcher_macos/ios" 505 | url_launcher_web: 506 | :path: ".symlinks/plugins/url_launcher_web/ios" 507 | 508 | SPEC CHECKSUMS: 509 | abseil: 18063d773f5366ff8736a050fe035a28f635fd27 510 | AppAuth: 73574f3013a1e65b9601a3ddc8b3158cce68c09d 511 | BoringSSL-GRPC: db8764df3204ccea016e1c8dd15d9a9ad63ff318 512 | cloud_firestore: 31454d48df21f3e1a900015e36143c0d46a304b7 513 | cloud_firestore_web: 9ec3dc7f5f98de5129339802d491c1204462bfec 514 | cloud_functions: cc76d33c7983c9057ef1163a560734595133091c 515 | cloud_functions_web: ec883775f4a3b3fad943918d2cdad4fbf51cdbf0 516 | Crashlytics: 540b7e5f5da5a042647227a5e3ac51d85eed06df 517 | Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74 518 | Firebase: f378c80340dd41c0ad0914af740c021eb282a04b 519 | firebase_analytics: dacdcfc524d722fff13dcff942f0dfa47e6be567 520 | firebase_auth: 4ee3a54d3f09434c508c284a62f895a741a30637 521 | firebase_auth_web: 0955c07bcc06e84af76b9d4e32e6f31518f2d7de 522 | firebase_core: 0d8be0e0d14c4902953aeb5ac5d7316d1fe4b978 523 | firebase_core_web: d501d8b946b60c8af265428ce483b0fff5ad52d1 524 | firebase_crashlytics: bd8c58df07f107cb7e1a20cb190b9a468e0a69fd 525 | firebase_performance: 96d9912b36a93e55201c24e386915d2a0dac1be9 526 | FirebaseABTesting: 821a3a3e4a145987e80fee3657c4de1cb9adf693 527 | FirebaseAnalytics: a1a0b3327ceb5cd5b4bacffdb293f6c909aa087d 528 | FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae 529 | FirebaseAuth: a6da11d78dfd956b7f7af3222a0f0b1c93ef8fc9 530 | FirebaseAuthInterop: a0f37ae05833af156e72028f648d313f7e7592e9 531 | FirebaseCore: 9f495d3afacb7b558711e6218ebb14b1c51b5802 532 | FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61 533 | FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 534 | FirebaseFirestore: 71925e62a5d1cbfaff46aa4d5107e525cc48356a 535 | FirebaseFunctions: 5af7c35d1c5e41608fecbb667eb6c4e672e318d0 536 | FirebaseInstallations: acb3216eb9784d3b1d2d2d635ff74fa892cc0c44 537 | FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be 538 | FirebasePerformance: 5652d2004001e886da6dca04f478c695f87c4702 539 | FirebaseRemoteConfig: 47abf7a04a9082091955ea555aa79cfdd249b19c 540 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 541 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 542 | google_sign_in: f32920a589fdf4ab2918ec6dc5e5b0d5b8040ff5 543 | google_sign_in_web: 52deb24929ac0992baff65c57956031c44ed44c3 544 | GoogleAppMeasurement: 6e68a94d0eaeb1d73ef6b0ed4f7334e29d63ae29 545 | GoogleDataTransport: b29a21d813e906014ca16c00897827e40e4a24ab 546 | GoogleDataTransportCCTSupport: 6f15a89b0ca35d6fa523e1f752ef818588885988 547 | GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213 548 | GoogleToolboxForMac: 800648f8b3127618c1b59c7f97684427630c5ea3 549 | GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e 550 | "gRPC-C++": 9dfe7b44821e7b3e44aacad2af29d2c21f7cde83 551 | gRPC-Core: c9aef9a261a1247e881b18059b84d597293c9947 552 | GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e 553 | GTMSessionFetcher: cea130bbfe5a7edc8d06d3f0d17288c32ffe9925 554 | leveldb-library: 55d93ee664b4007aac644a782d11da33fba316f7 555 | mono_kit: 67c15c1486e232d7f44ac47286933e80aa02f7a3 556 | nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd 557 | path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d 558 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 559 | PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 560 | Protobuf: 176220c526ad8bd09ab1fb40a978eac3fef665f7 561 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 562 | url_launcher: a1c0cc845906122c4784c542523d8cacbded5626 563 | url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 564 | url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c 565 | 566 | PODFILE CHECKSUM: c34e2287a9ccaa606aeceab922830efb9a6ff69a 567 | 568 | COCOAPODS: 1.9.1 569 | -------------------------------------------------------------------------------- /flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /flutter/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /flutter/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: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /flutter/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 | -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /flutter/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 | -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mono0926/google-tasks-clone/6483df65a5e0604d0b0f7f9c697731466e146016/flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /flutter/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. -------------------------------------------------------------------------------- /flutter/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 | -------------------------------------------------------------------------------- /flutter/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 | -------------------------------------------------------------------------------- /flutter/ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 528887329462-qet45ifecgpo6kgulhqcoqd1gsg0kqpo.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.528887329462-qet45ifecgpo6kgulhqcoqd1gsg0kqpo 9 | API_KEY 10 | AIzaSyCeX-TN2alhVMuZVMAANxr1v651RsXoPvg 11 | GCM_SENDER_ID 12 | 528887329462 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.mono0926.gtasks.dev 17 | PROJECT_ID 18 | gtasks-dev 19 | STORAGE_BUCKET 20 | gtasks-dev.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:528887329462:ios:a05769a60ff717bd 33 | DATABASE_URL 34 | https://gtasks-dev.firebaseio.com 35 | 36 | -------------------------------------------------------------------------------- /flutter/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 | google_tasks 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Editor 26 | CFBundleURLSchemes 27 | 28 | com.googleusercontent.apps.528887329462-qet45ifecgpo6kgulhqcoqd1gsg0kqpo 29 | 30 | 31 | 32 | CFBundleVersion 33 | $(FLUTTER_BUILD_NUMBER) 34 | LSRequiresIPhoneOS 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | UIViewControllerBasedStatusBarAppearance 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /flutter/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /flutter/ios/Runner/ja.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flutter/ios/Runner/ja.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flutter/lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_localizations/flutter_localizations.dart'; 4 | import 'package:mono_kit/mono_kit.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:route_observer_mixin/route_observer_mixin.dart'; 7 | 8 | import 'l10n/l10n.dart'; 9 | import 'router.dart'; 10 | import 'theme.dart'; 11 | import 'util/util.dart'; 12 | 13 | class App extends StatelessWidget { 14 | const App({Key key}) : super(key: key); 15 | @override 16 | Widget build(BuildContext context) { 17 | return MaterialApp( 18 | navigatorKey: Provider.of(context), 19 | theme: buildTheme(), 20 | darkTheme: ThemeData.dark(), 21 | navigatorObservers: [RouteObserverProvider.of(context)], 22 | builder: (context, child) => TextScaleFactor(child: child), 23 | localizationsDelegates: [ 24 | L10n.delegate, 25 | GlobalMaterialLocalizations.delegate, 26 | GlobalWidgetsLocalizations.delegate, 27 | GlobalCupertinoLocalizations.delegate, 28 | ], 29 | supportedLocales: const [ 30 | Locale('en'), 31 | Locale('ja'), 32 | ], 33 | onGenerateRoute: Provider.of(context).onGenerateRoute, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /flutter/lib/exception/app_exception.dart: -------------------------------------------------------------------------------- 1 | class AppException implements Exception { 2 | const AppException(this.code); 3 | final String code; 4 | 5 | static const notImplemented = AppException(AppErrorCodes.notImplemented); 6 | } 7 | 8 | class AppErrorCodes { 9 | static const notImplemented = 'notImplemented'; 10 | } 11 | -------------------------------------------------------------------------------- /flutter/lib/l10n/intl_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "en", 3 | "@@last_modified": "2019-10-21T21:15:53.353863", 4 | "buttonUndo": "UNDO", 5 | "@buttonUndo": { 6 | "type": "text", 7 | "placeholders": {} 8 | }, 9 | "buttonDone": "DONE", 10 | "@buttonDone": { 11 | "type": "text", 12 | "placeholders": {} 13 | }, 14 | "welcomeTitle": "Welcome to Tasks", 15 | "@welcomeTitle": { 16 | "type": "text", 17 | "placeholders": {} 18 | }, 19 | "copiedToPasteboard": "コピーしました。", 20 | "@copiedToPasteboard": { 21 | "type": "text", 22 | "placeholders": {} 23 | }, 24 | "accountExistsWithDifferentCredential": "同一のメールアドレスに紐付いた他のアカウントで登録済みなので、そちらからログインしてください。", 25 | "@accountExistsWithDifferentCredential": { 26 | "type": "text", 27 | "placeholders": {} 28 | }, 29 | "errorInvalidPassword": "パスワードが違います", 30 | "@errorInvalidPassword": { 31 | "type": "text", 32 | "placeholders": {} 33 | }, 34 | "errorTooManyRequests": "連続してログインに失敗したため、しばらく利用できません", 35 | "@errorTooManyRequests": { 36 | "type": "text", 37 | "placeholders": {} 38 | }, 39 | "errorRequiresRecentLogin": "その操作を実行するためには、再ログインが必要です。", 40 | "@errorRequiresRecentLogin": { 41 | "type": "text", 42 | "placeholders": {} 43 | }, 44 | "errorUserDisabled": "アプリの利用制限がかかっているため、ご利用できません。", 45 | "@errorUserDisabled": { 46 | "type": "text", 47 | "placeholders": {} 48 | }, 49 | "notImplemented": "未実装です", 50 | "@notImplemented": { 51 | "type": "text", 52 | "placeholders": {} 53 | }, 54 | "errorOccurred": "エラーが発生しました", 55 | "@errorOccurred": { 56 | "type": "text", 57 | "placeholders": {} 58 | }, 59 | "taskDeleted": "1件のタスクを削除しました", 60 | "@taskDeleted": { 61 | "type": "text", 62 | "placeholders": {} 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /flutter/lib/l10n/intl_ja.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "ja", 3 | "@@last_modified": "2019-08-15T10:43:43.964495", 4 | "welcomeTitle": "Tasksアプリへようこそ", 5 | "@welcomeTitle": { 6 | "type": "text", 7 | "placeholders": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /flutter/lib/l10n/intl_messages.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "messages", 3 | "@@last_modified": "2019-10-21T21:15:53.353863", 4 | "buttonUndo": "UNDO", 5 | "@buttonUndo": { 6 | "type": "text", 7 | "placeholders": {} 8 | }, 9 | "buttonDone": "DONE", 10 | "@buttonDone": { 11 | "type": "text", 12 | "placeholders": {} 13 | }, 14 | "welcomeTitle": "Welcome to Tasks", 15 | "@welcomeTitle": { 16 | "type": "text", 17 | "placeholders": {} 18 | }, 19 | "copiedToPasteboard": "コピーしました。", 20 | "@copiedToPasteboard": { 21 | "type": "text", 22 | "placeholders": {} 23 | }, 24 | "accountExistsWithDifferentCredential": "同一のメールアドレスに紐付いた他のアカウントで登録済みなので、そちらからログインしてください。", 25 | "@accountExistsWithDifferentCredential": { 26 | "type": "text", 27 | "placeholders": {} 28 | }, 29 | "errorInvalidPassword": "パスワードが違います", 30 | "@errorInvalidPassword": { 31 | "type": "text", 32 | "placeholders": {} 33 | }, 34 | "errorTooManyRequests": "連続してログインに失敗したため、しばらく利用できません", 35 | "@errorTooManyRequests": { 36 | "type": "text", 37 | "placeholders": {} 38 | }, 39 | "errorRequiresRecentLogin": "その操作を実行するためには、再ログインが必要です。", 40 | "@errorRequiresRecentLogin": { 41 | "type": "text", 42 | "placeholders": {} 43 | }, 44 | "errorUserDisabled": "アプリの利用制限がかかっているため、ご利用できません。", 45 | "@errorUserDisabled": { 46 | "type": "text", 47 | "placeholders": {} 48 | }, 49 | "notImplemented": "未実装です", 50 | "@notImplemented": { 51 | "type": "text", 52 | "placeholders": {} 53 | }, 54 | "errorOccurred": "エラーが発生しました", 55 | "@errorOccurred": { 56 | "type": "text", 57 | "placeholders": {} 58 | }, 59 | "taskDeleted": "1件のタスクを削除しました", 60 | "@taskDeleted": { 61 | "type": "text", 62 | "placeholders": {} 63 | } 64 | } -------------------------------------------------------------------------------- /flutter/lib/l10n/l10n.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:intl/intl.dart'; 5 | 6 | import 'l10n_delegate.dart'; 7 | import 'messages.dart'; 8 | import 'messages_all.dart'; 9 | 10 | export 'messages.dart'; 11 | 12 | /// アプリでの文言はこれ経由で取得する 13 | class L10n with Messages { 14 | /// 言語リソースを扱う 15 | /// 16 | /// localeは端末設定・アプリの指定を踏まえて最適なものが渡ってくる 17 | static Future load(Locale locale) async { 18 | final name = locale.countryCode == null || locale.countryCode.isEmpty 19 | ? locale.languageCode 20 | : locale.toString(); 21 | final localeName = Intl.canonicalizedLocale(name); 22 | 23 | // 言語リソース読み込み 24 | await initializeMessages(localeName); 25 | // デフォルト言語を設定 26 | Intl.defaultLocale = localeName; 27 | // 自身を返す 28 | return L10n(); 29 | } 30 | 31 | // Widgetツリーから自身を取り出す 32 | static L10n of(BuildContext context) { 33 | return Localizations.of(context, L10n); 34 | } 35 | 36 | static const LocalizationsDelegate delegate = L10nDelegate(); 37 | } 38 | -------------------------------------------------------------------------------- /flutter/lib/l10n/l10n_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'l10n.dart'; 4 | 5 | class L10nDelegate extends LocalizationsDelegate { 6 | const L10nDelegate(); 7 | 8 | @override 9 | bool isSupported(Locale locale) => ['en', 'ja'].contains(locale.languageCode); 10 | 11 | @override 12 | Future load(Locale locale) => L10n.load(locale); 13 | 14 | @override 15 | bool shouldReload(L10nDelegate old) => false; 16 | } 17 | -------------------------------------------------------------------------------- /flutter/lib/l10n/messages.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | mixin Messages { 5 | String get buttonUndo => Intl.message( 6 | 'UNDO', 7 | name: 'buttonUndo', 8 | ); 9 | String get buttonDone => Intl.message( 10 | 'DONE', 11 | name: 'buttonDone', 12 | ); 13 | String get welcomeTitle => Intl.message( 14 | 'Welcome to Tasks', 15 | name: 'welcomeTitle', 16 | ); 17 | String get copiedToPasteboard => Intl.message( 18 | 'コピーしました。', 19 | name: 'copiedToPasteboard', 20 | ); 21 | 22 | String get accountExistsWithDifferentCredential => Intl.message( 23 | '同一のメールアドレスに紐付いた他のアカウントで登録済みなので、' 24 | 'そちらからログインしてください。', 25 | name: 'accountExistsWithDifferentCredential', 26 | ); 27 | String get errorInvalidPassword => Intl.message( 28 | 'パスワードが違います', 29 | name: 'errorInvalidPassword', 30 | ); 31 | String get errorTooManyRequests => Intl.message( 32 | '連続してログインに失敗したため、しばらく利用できません', 33 | name: 'errorTooManyRequests', 34 | ); 35 | String get errorRequiresRecentLogin => Intl.message( 36 | 'その操作を実行するためには、再ログインが必要です。', 37 | name: 'errorRequiresRecentLogin', 38 | ); 39 | String get errorUserDisabled => Intl.message( 40 | 'アプリの利用制限がかかっているため、ご利用できません。', 41 | name: 'errorUserDisabled', 42 | ); 43 | String get notImplemented => Intl.message( 44 | '未実装です', 45 | name: 'notImplemented', 46 | ); 47 | 48 | String get errorOccurred => Intl.message( 49 | 'エラーが発生しました', 50 | name: 'errorOccurred', 51 | ); 52 | String get taskDeleted => Intl.message( 53 | '1件のタスクを削除しました', 54 | name: 'taskDeleted', 55 | ); 56 | 57 | String timeOfDay(TimeOfDay time) => ''; 58 | } 59 | -------------------------------------------------------------------------------- /flutter/lib/l10n/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | // Ignore issues from commonly used lints in this file. 6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new 7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering 8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment 9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases 10 | // ignore_for_file:comment_references 11 | 12 | import 'dart:async'; 13 | 14 | import 'package:intl/intl.dart'; 15 | import 'package:intl/message_lookup_by_library.dart'; 16 | import 'package:intl/src/intl_helpers.dart'; 17 | 18 | import 'messages_en.dart' as messages_en; 19 | import 'messages_ja.dart' as messages_ja; 20 | import 'messages_messages.dart' as messages_messages; 21 | 22 | typedef Future LibraryLoader(); 23 | Map _deferredLibraries = { 24 | 'en': () => new Future.value(null), 25 | 'ja': () => new Future.value(null), 26 | 'messages': () => new Future.value(null), 27 | }; 28 | 29 | MessageLookupByLibrary _findExact(String localeName) { 30 | switch (localeName) { 31 | case 'en': 32 | return messages_en.messages; 33 | case 'ja': 34 | return messages_ja.messages; 35 | case 'messages': 36 | return messages_messages.messages; 37 | default: 38 | return null; 39 | } 40 | } 41 | 42 | /// User programs should call this before using [localeName] for messages. 43 | Future initializeMessages(String localeName) async { 44 | var availableLocale = Intl.verifiedLocale( 45 | localeName, (locale) => _deferredLibraries[locale] != null, 46 | onFailure: (_) => null); 47 | if (availableLocale == null) { 48 | return new Future.value(false); 49 | } 50 | var lib = _deferredLibraries[availableLocale]; 51 | await (lib == null ? new Future.value(false) : lib()); 52 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 53 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 54 | return new Future.value(true); 55 | } 56 | 57 | bool _messagesExistFor(String locale) { 58 | try { 59 | return _findExact(locale) != null; 60 | } catch (e) { 61 | return false; 62 | } 63 | } 64 | 65 | MessageLookupByLibrary _findGeneratedMessagesFor(String locale) { 66 | var actualLocale = 67 | Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); 68 | if (actualLocale == null) return null; 69 | return _findExact(actualLocale); 70 | } 71 | -------------------------------------------------------------------------------- /flutter/lib/l10n/messages_en.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a en locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names 11 | 12 | import 'package:intl/intl.dart'; 13 | import 'package:intl/message_lookup_by_library.dart'; 14 | 15 | final messages = new MessageLookup(); 16 | 17 | typedef String MessageIfAbsent(String messageStr, List args); 18 | 19 | class MessageLookup extends MessageLookupByLibrary { 20 | String get localeName => 'en'; 21 | 22 | final messages = _notInlinedMessages(_notInlinedMessages); 23 | static _notInlinedMessages(_) => { 24 | "accountExistsWithDifferentCredential": 25 | MessageLookupByLibrary.simpleMessage( 26 | "同一のメールアドレスに紐付いた他のアカウントで登録済みなので、そちらからログインしてください。"), 27 | "buttonDone": MessageLookupByLibrary.simpleMessage("DONE"), 28 | "buttonUndo": MessageLookupByLibrary.simpleMessage("UNDO"), 29 | "copiedToPasteboard": MessageLookupByLibrary.simpleMessage("コピーしました。"), 30 | "errorInvalidPassword": 31 | MessageLookupByLibrary.simpleMessage("パスワードが違います"), 32 | "errorOccurred": MessageLookupByLibrary.simpleMessage("エラーが発生しました"), 33 | "errorRequiresRecentLogin": 34 | MessageLookupByLibrary.simpleMessage("その操作を実行するためには、再ログインが必要です。"), 35 | "errorTooManyRequests": 36 | MessageLookupByLibrary.simpleMessage("連続してログインに失敗したため、しばらく利用できません"), 37 | "errorUserDisabled": 38 | MessageLookupByLibrary.simpleMessage("アプリの利用制限がかかっているため、ご利用できません。"), 39 | "notImplemented": MessageLookupByLibrary.simpleMessage("未実装です"), 40 | "taskDeleted": MessageLookupByLibrary.simpleMessage("1件のタスクを削除しました"), 41 | "welcomeTitle": MessageLookupByLibrary.simpleMessage("Welcome to Tasks") 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /flutter/lib/l10n/messages_ja.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a ja locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names 11 | 12 | import 'package:intl/intl.dart'; 13 | import 'package:intl/message_lookup_by_library.dart'; 14 | 15 | final messages = new MessageLookup(); 16 | 17 | typedef String MessageIfAbsent(String messageStr, List args); 18 | 19 | class MessageLookup extends MessageLookupByLibrary { 20 | String get localeName => 'ja'; 21 | 22 | final messages = _notInlinedMessages(_notInlinedMessages); 23 | static _notInlinedMessages(_) => { 24 | "welcomeTitle": MessageLookupByLibrary.simpleMessage("Tasksアプリへようこそ") 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /flutter/lib/l10n/messages_messages.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a messages locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names 11 | 12 | import 'package:intl/intl.dart'; 13 | import 'package:intl/message_lookup_by_library.dart'; 14 | 15 | final messages = new MessageLookup(); 16 | 17 | typedef String MessageIfAbsent(String messageStr, List args); 18 | 19 | class MessageLookup extends MessageLookupByLibrary { 20 | String get localeName => 'messages'; 21 | 22 | final messages = _notInlinedMessages(_notInlinedMessages); 23 | static _notInlinedMessages(_) => { 24 | "accountExistsWithDifferentCredential": 25 | MessageLookupByLibrary.simpleMessage( 26 | "同一のメールアドレスに紐付いた他のアカウントで登録済みなので、そちらからログインしてください。"), 27 | "buttonDone": MessageLookupByLibrary.simpleMessage("DONE"), 28 | "buttonUndo": MessageLookupByLibrary.simpleMessage("UNDO"), 29 | "copiedToPasteboard": MessageLookupByLibrary.simpleMessage("コピーしました。"), 30 | "errorInvalidPassword": 31 | MessageLookupByLibrary.simpleMessage("パスワードが違います"), 32 | "errorOccurred": MessageLookupByLibrary.simpleMessage("エラーが発生しました"), 33 | "errorRequiresRecentLogin": 34 | MessageLookupByLibrary.simpleMessage("その操作を実行するためには、再ログインが必要です。"), 35 | "errorTooManyRequests": 36 | MessageLookupByLibrary.simpleMessage("連続してログインに失敗したため、しばらく利用できません"), 37 | "errorUserDisabled": 38 | MessageLookupByLibrary.simpleMessage("アプリの利用制限がかかっているため、ご利用できません。"), 39 | "notImplemented": MessageLookupByLibrary.simpleMessage("未実装です"), 40 | "taskDeleted": MessageLookupByLibrary.simpleMessage("1件のタスクを削除しました"), 41 | "welcomeTitle": MessageLookupByLibrary.simpleMessage("Welcome to Tasks") 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /flutter/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:google_tasks/model/model.dart'; 4 | import 'package:google_tasks/router.dart'; 5 | import 'package:google_tasks/util/util.dart'; 6 | 7 | import 'app.dart'; 8 | import 'model/notifier/notifier.dart'; 9 | 10 | void main() { 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | final originalFlutterError = FlutterError.onError; 13 | FlutterError.onError = (error) { 14 | originalFlutterError(error); 15 | Crashlytics.instance.recordFlutterError(error); 16 | }; 17 | runApp( 18 | MultiProvider( 19 | providers: [ 20 | Provider(create: (context) => GlobalKey()), 21 | RouteObserverProvider(), 22 | DisposableProvider(create: (context) => Authenticator()), 23 | DisposableProvider( 24 | create: (context) => Router( 25 | authenticator: Provider.of(context, listen: false), 26 | navigatorKey: 27 | Provider.of>(context, listen: false), 28 | ), 29 | ), 30 | Provider(create: (context) => UserObserver()), 31 | ChangeNotifierProvider( 32 | create: (context) => AccountNotifier( 33 | authenticator: Provider.of(context, listen: false), 34 | userObserver: Provider.of(context, listen: false), 35 | ), 36 | ), 37 | Provider( 38 | create: (context) => TasksService( 39 | authenticator: Provider.of(context, listen: false), 40 | ), 41 | ), 42 | ], 43 | child: const App(), 44 | ), 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/entity.dart: -------------------------------------------------------------------------------- 1 | export 'user/task/task.dart'; 2 | export 'user/user.dart'; 3 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/task/due.dart: -------------------------------------------------------------------------------- 1 | import 'package:firestore_ref/firestore_ref.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | 5 | part 'due.freezed.dart'; 6 | part 'due.g.dart'; 7 | 8 | @freezed 9 | abstract class Due with _$Due { 10 | const factory Due( 11 | @TimestampConverter() DateTime dateTime, { 12 | @Default(false) bool includeTime, 13 | }) = _Due; 14 | 15 | factory Due.fromJson(Map json) => _$DueFromJson(json); 16 | } 17 | 18 | class DueField { 19 | static const dateTime = 'dateTime'; 20 | static const includeTime = 'includeTime'; 21 | } 22 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/task/due.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named 3 | 4 | part of 'due.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | Due _$DueFromJson(Map json) { 12 | return _Due.fromJson(json); 13 | } 14 | 15 | class _$DueTearOff { 16 | const _$DueTearOff(); 17 | 18 | _Due call(@TimestampConverter() DateTime dateTime, 19 | {bool includeTime = false}) { 20 | return _Due( 21 | dateTime, 22 | includeTime: includeTime, 23 | ); 24 | } 25 | } 26 | 27 | // ignore: unused_element 28 | const $Due = _$DueTearOff(); 29 | 30 | mixin _$Due { 31 | @TimestampConverter() 32 | DateTime get dateTime; 33 | bool get includeTime; 34 | 35 | Map toJson(); 36 | $DueCopyWith get copyWith; 37 | } 38 | 39 | abstract class $DueCopyWith<$Res> { 40 | factory $DueCopyWith(Due value, $Res Function(Due) then) = 41 | _$DueCopyWithImpl<$Res>; 42 | $Res call({@TimestampConverter() DateTime dateTime, bool includeTime}); 43 | } 44 | 45 | class _$DueCopyWithImpl<$Res> implements $DueCopyWith<$Res> { 46 | _$DueCopyWithImpl(this._value, this._then); 47 | 48 | final Due _value; 49 | // ignore: unused_field 50 | final $Res Function(Due) _then; 51 | 52 | @override 53 | $Res call({ 54 | Object dateTime = freezed, 55 | Object includeTime = freezed, 56 | }) { 57 | return _then(_value.copyWith( 58 | dateTime: dateTime == freezed ? _value.dateTime : dateTime as DateTime, 59 | includeTime: 60 | includeTime == freezed ? _value.includeTime : includeTime as bool, 61 | )); 62 | } 63 | } 64 | 65 | abstract class _$DueCopyWith<$Res> implements $DueCopyWith<$Res> { 66 | factory _$DueCopyWith(_Due value, $Res Function(_Due) then) = 67 | __$DueCopyWithImpl<$Res>; 68 | @override 69 | $Res call({@TimestampConverter() DateTime dateTime, bool includeTime}); 70 | } 71 | 72 | class __$DueCopyWithImpl<$Res> extends _$DueCopyWithImpl<$Res> 73 | implements _$DueCopyWith<$Res> { 74 | __$DueCopyWithImpl(_Due _value, $Res Function(_Due) _then) 75 | : super(_value, (v) => _then(v as _Due)); 76 | 77 | @override 78 | _Due get _value => super._value as _Due; 79 | 80 | @override 81 | $Res call({ 82 | Object dateTime = freezed, 83 | Object includeTime = freezed, 84 | }) { 85 | return _then(_Due( 86 | dateTime == freezed ? _value.dateTime : dateTime as DateTime, 87 | includeTime: 88 | includeTime == freezed ? _value.includeTime : includeTime as bool, 89 | )); 90 | } 91 | } 92 | 93 | @JsonSerializable() 94 | class _$_Due with DiagnosticableTreeMixin implements _Due { 95 | const _$_Due(@TimestampConverter() this.dateTime, {this.includeTime = false}) 96 | : assert(dateTime != null), 97 | assert(includeTime != null); 98 | 99 | factory _$_Due.fromJson(Map json) => _$_$_DueFromJson(json); 100 | 101 | @override 102 | @TimestampConverter() 103 | final DateTime dateTime; 104 | @JsonKey(defaultValue: false) 105 | @override 106 | final bool includeTime; 107 | 108 | @override 109 | String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { 110 | return 'Due(dateTime: $dateTime, includeTime: $includeTime)'; 111 | } 112 | 113 | @override 114 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 115 | super.debugFillProperties(properties); 116 | properties 117 | ..add(DiagnosticsProperty('type', 'Due')) 118 | ..add(DiagnosticsProperty('dateTime', dateTime)) 119 | ..add(DiagnosticsProperty('includeTime', includeTime)); 120 | } 121 | 122 | @override 123 | bool operator ==(dynamic other) { 124 | return identical(this, other) || 125 | (other is _Due && 126 | (identical(other.dateTime, dateTime) || 127 | const DeepCollectionEquality() 128 | .equals(other.dateTime, dateTime)) && 129 | (identical(other.includeTime, includeTime) || 130 | const DeepCollectionEquality() 131 | .equals(other.includeTime, includeTime))); 132 | } 133 | 134 | @override 135 | int get hashCode => 136 | runtimeType.hashCode ^ 137 | const DeepCollectionEquality().hash(dateTime) ^ 138 | const DeepCollectionEquality().hash(includeTime); 139 | 140 | @override 141 | _$DueCopyWith<_Due> get copyWith => 142 | __$DueCopyWithImpl<_Due>(this, _$identity); 143 | 144 | @override 145 | Map toJson() { 146 | return _$_$_DueToJson(this); 147 | } 148 | } 149 | 150 | abstract class _Due implements Due { 151 | const factory _Due(@TimestampConverter() DateTime dateTime, 152 | {bool includeTime}) = _$_Due; 153 | 154 | factory _Due.fromJson(Map json) = _$_Due.fromJson; 155 | 156 | @override 157 | @TimestampConverter() 158 | DateTime get dateTime; 159 | @override 160 | bool get includeTime; 161 | @override 162 | _$DueCopyWith<_Due> get copyWith; 163 | } 164 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/task/due.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'due.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Due _$_$_DueFromJson(Map json) { 10 | return _$_Due( 11 | const TimestampConverter().fromJson(json['dateTime'] as Timestamp), 12 | includeTime: json['includeTime'] as bool ?? false, 13 | ); 14 | } 15 | 16 | Map _$_$_DueToJson(_$_Due instance) => { 17 | 'dateTime': const TimestampConverter().toJson(instance.dateTime), 18 | 'includeTime': instance.includeTime, 19 | }; 20 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/task/task.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:google_tasks/model/model.dart'; 4 | import 'package:json_annotation/json_annotation.dart'; 5 | 6 | import 'due.dart'; 7 | 8 | export 'due.dart'; 9 | export 'task_doc.dart'; 10 | 11 | part 'task.freezed.dart'; 12 | part 'task.g.dart'; 13 | 14 | @freezed 15 | abstract class Task with _$Task { 16 | const factory Task({ 17 | @Default('') String title, 18 | String details, 19 | Due due, 20 | @TimestampConverter() DateTime createdAt, 21 | @TimestampConverter() DateTime updatedAt, 22 | }) = _Task; 23 | 24 | factory Task.fromJson(Map json) => _$TaskFromJson(json); 25 | } 26 | 27 | class TaskField { 28 | static const title = 'title'; 29 | static const details = 'details'; 30 | static const due = 'due'; 31 | } 32 | 33 | class TaskRef extends DocumentRef { 34 | TaskRef({ 35 | @required String id, 36 | @required TasksRef collectionRef, 37 | }) : super( 38 | id: id, 39 | collectionRef: collectionRef, 40 | ); 41 | } 42 | 43 | class TasksRef extends CollectionRef { 44 | TasksRef._({ 45 | @required CollectionReference ref, 46 | }) : super( 47 | ref, 48 | decoder: (snapshot) => TaskDoc( 49 | snapshot.documentID, 50 | Task.fromJson(snapshot.data), 51 | ), 52 | encoder: (task) => replacingTimestamp( 53 | json: task.toJson(), 54 | createdAt: task.createdAt, 55 | ), 56 | ); 57 | 58 | factory TasksRef.ref(UserRef userRef) { 59 | return TasksRef._( 60 | ref: userRef.ref.collection(collection), 61 | ); 62 | } 63 | 64 | static const collection = 'tasks'; 65 | 66 | @override 67 | TaskRef docRef([String id]) { 68 | return TaskRef( 69 | id: id, 70 | collectionRef: this, 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/task/task.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named 3 | 4 | part of 'task.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | Task _$TaskFromJson(Map json) { 12 | return _Task.fromJson(json); 13 | } 14 | 15 | class _$TaskTearOff { 16 | const _$TaskTearOff(); 17 | 18 | _Task call( 19 | {String title = '', 20 | String details, 21 | Due due, 22 | @TimestampConverter() DateTime createdAt, 23 | @TimestampConverter() DateTime updatedAt}) { 24 | return _Task( 25 | title: title, 26 | details: details, 27 | due: due, 28 | createdAt: createdAt, 29 | updatedAt: updatedAt, 30 | ); 31 | } 32 | } 33 | 34 | // ignore: unused_element 35 | const $Task = _$TaskTearOff(); 36 | 37 | mixin _$Task { 38 | String get title; 39 | String get details; 40 | Due get due; 41 | @TimestampConverter() 42 | DateTime get createdAt; 43 | @TimestampConverter() 44 | DateTime get updatedAt; 45 | 46 | Map toJson(); 47 | $TaskCopyWith get copyWith; 48 | } 49 | 50 | abstract class $TaskCopyWith<$Res> { 51 | factory $TaskCopyWith(Task value, $Res Function(Task) then) = 52 | _$TaskCopyWithImpl<$Res>; 53 | $Res call( 54 | {String title, 55 | String details, 56 | Due due, 57 | @TimestampConverter() DateTime createdAt, 58 | @TimestampConverter() DateTime updatedAt}); 59 | 60 | $DueCopyWith<$Res> get due; 61 | } 62 | 63 | class _$TaskCopyWithImpl<$Res> implements $TaskCopyWith<$Res> { 64 | _$TaskCopyWithImpl(this._value, this._then); 65 | 66 | final Task _value; 67 | // ignore: unused_field 68 | final $Res Function(Task) _then; 69 | 70 | @override 71 | $Res call({ 72 | Object title = freezed, 73 | Object details = freezed, 74 | Object due = freezed, 75 | Object createdAt = freezed, 76 | Object updatedAt = freezed, 77 | }) { 78 | return _then(_value.copyWith( 79 | title: title == freezed ? _value.title : title as String, 80 | details: details == freezed ? _value.details : details as String, 81 | due: due == freezed ? _value.due : due as Due, 82 | createdAt: 83 | createdAt == freezed ? _value.createdAt : createdAt as DateTime, 84 | updatedAt: 85 | updatedAt == freezed ? _value.updatedAt : updatedAt as DateTime, 86 | )); 87 | } 88 | 89 | @override 90 | $DueCopyWith<$Res> get due { 91 | if (_value.due == null) { 92 | return null; 93 | } 94 | return $DueCopyWith<$Res>(_value.due, (value) { 95 | return _then(_value.copyWith(due: value)); 96 | }); 97 | } 98 | } 99 | 100 | abstract class _$TaskCopyWith<$Res> implements $TaskCopyWith<$Res> { 101 | factory _$TaskCopyWith(_Task value, $Res Function(_Task) then) = 102 | __$TaskCopyWithImpl<$Res>; 103 | @override 104 | $Res call( 105 | {String title, 106 | String details, 107 | Due due, 108 | @TimestampConverter() DateTime createdAt, 109 | @TimestampConverter() DateTime updatedAt}); 110 | 111 | @override 112 | $DueCopyWith<$Res> get due; 113 | } 114 | 115 | class __$TaskCopyWithImpl<$Res> extends _$TaskCopyWithImpl<$Res> 116 | implements _$TaskCopyWith<$Res> { 117 | __$TaskCopyWithImpl(_Task _value, $Res Function(_Task) _then) 118 | : super(_value, (v) => _then(v as _Task)); 119 | 120 | @override 121 | _Task get _value => super._value as _Task; 122 | 123 | @override 124 | $Res call({ 125 | Object title = freezed, 126 | Object details = freezed, 127 | Object due = freezed, 128 | Object createdAt = freezed, 129 | Object updatedAt = freezed, 130 | }) { 131 | return _then(_Task( 132 | title: title == freezed ? _value.title : title as String, 133 | details: details == freezed ? _value.details : details as String, 134 | due: due == freezed ? _value.due : due as Due, 135 | createdAt: 136 | createdAt == freezed ? _value.createdAt : createdAt as DateTime, 137 | updatedAt: 138 | updatedAt == freezed ? _value.updatedAt : updatedAt as DateTime, 139 | )); 140 | } 141 | } 142 | 143 | @JsonSerializable() 144 | class _$_Task with DiagnosticableTreeMixin implements _Task { 145 | const _$_Task( 146 | {this.title = '', 147 | this.details, 148 | this.due, 149 | @TimestampConverter() this.createdAt, 150 | @TimestampConverter() this.updatedAt}) 151 | : assert(title != null); 152 | 153 | factory _$_Task.fromJson(Map json) => 154 | _$_$_TaskFromJson(json); 155 | 156 | @JsonKey(defaultValue: '') 157 | @override 158 | final String title; 159 | @override 160 | final String details; 161 | @override 162 | final Due due; 163 | @override 164 | @TimestampConverter() 165 | final DateTime createdAt; 166 | @override 167 | @TimestampConverter() 168 | final DateTime updatedAt; 169 | 170 | @override 171 | String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { 172 | return 'Task(title: $title, details: $details, due: $due, createdAt: $createdAt, updatedAt: $updatedAt)'; 173 | } 174 | 175 | @override 176 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 177 | super.debugFillProperties(properties); 178 | properties 179 | ..add(DiagnosticsProperty('type', 'Task')) 180 | ..add(DiagnosticsProperty('title', title)) 181 | ..add(DiagnosticsProperty('details', details)) 182 | ..add(DiagnosticsProperty('due', due)) 183 | ..add(DiagnosticsProperty('createdAt', createdAt)) 184 | ..add(DiagnosticsProperty('updatedAt', updatedAt)); 185 | } 186 | 187 | @override 188 | bool operator ==(dynamic other) { 189 | return identical(this, other) || 190 | (other is _Task && 191 | (identical(other.title, title) || 192 | const DeepCollectionEquality().equals(other.title, title)) && 193 | (identical(other.details, details) || 194 | const DeepCollectionEquality() 195 | .equals(other.details, details)) && 196 | (identical(other.due, due) || 197 | const DeepCollectionEquality().equals(other.due, due)) && 198 | (identical(other.createdAt, createdAt) || 199 | const DeepCollectionEquality() 200 | .equals(other.createdAt, createdAt)) && 201 | (identical(other.updatedAt, updatedAt) || 202 | const DeepCollectionEquality() 203 | .equals(other.updatedAt, updatedAt))); 204 | } 205 | 206 | @override 207 | int get hashCode => 208 | runtimeType.hashCode ^ 209 | const DeepCollectionEquality().hash(title) ^ 210 | const DeepCollectionEquality().hash(details) ^ 211 | const DeepCollectionEquality().hash(due) ^ 212 | const DeepCollectionEquality().hash(createdAt) ^ 213 | const DeepCollectionEquality().hash(updatedAt); 214 | 215 | @override 216 | _$TaskCopyWith<_Task> get copyWith => 217 | __$TaskCopyWithImpl<_Task>(this, _$identity); 218 | 219 | @override 220 | Map toJson() { 221 | return _$_$_TaskToJson(this); 222 | } 223 | } 224 | 225 | abstract class _Task implements Task { 226 | const factory _Task( 227 | {String title, 228 | String details, 229 | Due due, 230 | @TimestampConverter() DateTime createdAt, 231 | @TimestampConverter() DateTime updatedAt}) = _$_Task; 232 | 233 | factory _Task.fromJson(Map json) = _$_Task.fromJson; 234 | 235 | @override 236 | String get title; 237 | @override 238 | String get details; 239 | @override 240 | Due get due; 241 | @override 242 | @TimestampConverter() 243 | DateTime get createdAt; 244 | @override 245 | @TimestampConverter() 246 | DateTime get updatedAt; 247 | @override 248 | _$TaskCopyWith<_Task> get copyWith; 249 | } 250 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/task/task.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'task.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Task _$_$_TaskFromJson(Map json) { 10 | return _$_Task( 11 | title: json['title'] as String ?? '', 12 | details: json['details'] as String, 13 | due: json['due'] == null 14 | ? null 15 | : Due.fromJson((json['due'] as Map)?.map( 16 | (k, e) => MapEntry(k as String, e), 17 | )), 18 | createdAt: 19 | const TimestampConverter().fromJson(json['createdAt'] as Timestamp), 20 | updatedAt: 21 | const TimestampConverter().fromJson(json['updatedAt'] as Timestamp), 22 | ); 23 | } 24 | 25 | Map _$_$_TaskToJson(_$_Task instance) => { 26 | 'title': instance.title, 27 | 'details': instance.details, 28 | 'due': instance.due?.toJson(), 29 | 'createdAt': const TimestampConverter().toJson(instance.createdAt), 30 | 'updatedAt': const TimestampConverter().toJson(instance.updatedAt), 31 | }; 32 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/task/task_doc.dart: -------------------------------------------------------------------------------- 1 | import 'package:google_tasks/model/model.dart'; 2 | 3 | import 'task.dart'; 4 | 5 | class TaskDoc extends Document { 6 | const TaskDoc( 7 | String id, 8 | Task entity, 9 | ) : super( 10 | id, 11 | entity, 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:firestore_ref/firestore_ref.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | import 'package:google_tasks/model/entity/entity.dart'; 5 | import 'package:json_annotation/json_annotation.dart'; 6 | 7 | import 'user_doc.dart'; 8 | 9 | export 'user_doc.dart'; 10 | 11 | part 'user.freezed.dart'; 12 | part 'user.g.dart'; 13 | 14 | @freezed 15 | abstract class User with _$User { 16 | const factory User({ 17 | @required String name, 18 | @required String imageUrl, 19 | }) = _User; 20 | 21 | factory User.fromJson(Map json) => _$UserFromJson(json); 22 | } 23 | 24 | class UserField { 25 | static const name = 'name'; 26 | static const imageUrl = 'imageUrl'; 27 | } 28 | 29 | class UserRef extends DocumentRef { 30 | UserRef({ 31 | @required String id, 32 | @required UsersRef usersRef, 33 | }) : super( 34 | id: id, 35 | collectionRef: usersRef, 36 | ); 37 | 38 | TasksRef get tasksRef => TasksRef.ref(this); 39 | } 40 | 41 | class UsersRef extends CollectionRef { 42 | UsersRef._({ 43 | @required CollectionReference ref, 44 | }) : super( 45 | ref, 46 | decoder: (snapshot) => UserDoc( 47 | snapshot.documentID, 48 | User.fromJson(snapshot.data), 49 | ), 50 | encoder: (user) => user.toJson(), 51 | ); 52 | 53 | factory UsersRef.ref() { 54 | return UsersRef._( 55 | ref: Firestore.instance.collection(collection), 56 | ); 57 | } 58 | 59 | static const collection = 'users'; 60 | 61 | @override 62 | UserRef docRef([String id]) { 63 | return UserRef( 64 | id: id, 65 | usersRef: this, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/user.freezed.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // ignore_for_file: deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named 3 | 4 | part of 'user.dart'; 5 | 6 | // ************************************************************************** 7 | // FreezedGenerator 8 | // ************************************************************************** 9 | 10 | T _$identity(T value) => value; 11 | User _$UserFromJson(Map json) { 12 | return _User.fromJson(json); 13 | } 14 | 15 | class _$UserTearOff { 16 | const _$UserTearOff(); 17 | 18 | _User call({@required String name, @required String imageUrl}) { 19 | return _User( 20 | name: name, 21 | imageUrl: imageUrl, 22 | ); 23 | } 24 | } 25 | 26 | // ignore: unused_element 27 | const $User = _$UserTearOff(); 28 | 29 | mixin _$User { 30 | String get name; 31 | String get imageUrl; 32 | 33 | Map toJson(); 34 | $UserCopyWith get copyWith; 35 | } 36 | 37 | abstract class $UserCopyWith<$Res> { 38 | factory $UserCopyWith(User value, $Res Function(User) then) = 39 | _$UserCopyWithImpl<$Res>; 40 | $Res call({String name, String imageUrl}); 41 | } 42 | 43 | class _$UserCopyWithImpl<$Res> implements $UserCopyWith<$Res> { 44 | _$UserCopyWithImpl(this._value, this._then); 45 | 46 | final User _value; 47 | // ignore: unused_field 48 | final $Res Function(User) _then; 49 | 50 | @override 51 | $Res call({ 52 | Object name = freezed, 53 | Object imageUrl = freezed, 54 | }) { 55 | return _then(_value.copyWith( 56 | name: name == freezed ? _value.name : name as String, 57 | imageUrl: imageUrl == freezed ? _value.imageUrl : imageUrl as String, 58 | )); 59 | } 60 | } 61 | 62 | abstract class _$UserCopyWith<$Res> implements $UserCopyWith<$Res> { 63 | factory _$UserCopyWith(_User value, $Res Function(_User) then) = 64 | __$UserCopyWithImpl<$Res>; 65 | @override 66 | $Res call({String name, String imageUrl}); 67 | } 68 | 69 | class __$UserCopyWithImpl<$Res> extends _$UserCopyWithImpl<$Res> 70 | implements _$UserCopyWith<$Res> { 71 | __$UserCopyWithImpl(_User _value, $Res Function(_User) _then) 72 | : super(_value, (v) => _then(v as _User)); 73 | 74 | @override 75 | _User get _value => super._value as _User; 76 | 77 | @override 78 | $Res call({ 79 | Object name = freezed, 80 | Object imageUrl = freezed, 81 | }) { 82 | return _then(_User( 83 | name: name == freezed ? _value.name : name as String, 84 | imageUrl: imageUrl == freezed ? _value.imageUrl : imageUrl as String, 85 | )); 86 | } 87 | } 88 | 89 | @JsonSerializable() 90 | class _$_User with DiagnosticableTreeMixin implements _User { 91 | const _$_User({@required this.name, @required this.imageUrl}) 92 | : assert(name != null), 93 | assert(imageUrl != null); 94 | 95 | factory _$_User.fromJson(Map json) => 96 | _$_$_UserFromJson(json); 97 | 98 | @override 99 | final String name; 100 | @override 101 | final String imageUrl; 102 | 103 | @override 104 | String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { 105 | return 'User(name: $name, imageUrl: $imageUrl)'; 106 | } 107 | 108 | @override 109 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 110 | super.debugFillProperties(properties); 111 | properties 112 | ..add(DiagnosticsProperty('type', 'User')) 113 | ..add(DiagnosticsProperty('name', name)) 114 | ..add(DiagnosticsProperty('imageUrl', imageUrl)); 115 | } 116 | 117 | @override 118 | bool operator ==(dynamic other) { 119 | return identical(this, other) || 120 | (other is _User && 121 | (identical(other.name, name) || 122 | const DeepCollectionEquality().equals(other.name, name)) && 123 | (identical(other.imageUrl, imageUrl) || 124 | const DeepCollectionEquality() 125 | .equals(other.imageUrl, imageUrl))); 126 | } 127 | 128 | @override 129 | int get hashCode => 130 | runtimeType.hashCode ^ 131 | const DeepCollectionEquality().hash(name) ^ 132 | const DeepCollectionEquality().hash(imageUrl); 133 | 134 | @override 135 | _$UserCopyWith<_User> get copyWith => 136 | __$UserCopyWithImpl<_User>(this, _$identity); 137 | 138 | @override 139 | Map toJson() { 140 | return _$_$_UserToJson(this); 141 | } 142 | } 143 | 144 | abstract class _User implements User { 145 | const factory _User({@required String name, @required String imageUrl}) = 146 | _$_User; 147 | 148 | factory _User.fromJson(Map json) = _$_User.fromJson; 149 | 150 | @override 151 | String get name; 152 | @override 153 | String get imageUrl; 154 | @override 155 | _$UserCopyWith<_User> get copyWith; 156 | } 157 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/user.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_User _$_$_UserFromJson(Map json) { 10 | return _$_User( 11 | name: json['name'] as String, 12 | imageUrl: json['imageUrl'] as String, 13 | ); 14 | } 15 | 16 | Map _$_$_UserToJson(_$_User instance) => { 17 | 'name': instance.name, 18 | 'imageUrl': instance.imageUrl, 19 | }; 20 | -------------------------------------------------------------------------------- /flutter/lib/model/entity/user/user_doc.dart: -------------------------------------------------------------------------------- 1 | import 'package:google_tasks/model/model.dart'; 2 | 3 | import 'user.dart'; 4 | 5 | class UserDoc extends Document { 6 | const UserDoc( 7 | String id, 8 | User entity, 9 | ) : super( 10 | id, 11 | entity, 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /flutter/lib/model/model.dart: -------------------------------------------------------------------------------- 1 | export 'package:firestore_ref/firestore_ref.dart'; 2 | 3 | export 'entity/entity.dart'; 4 | export 'notifier/notifier.dart'; 5 | export 'service/service.dart'; 6 | -------------------------------------------------------------------------------- /flutter/lib/model/notifier/account_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:google_tasks/model/entity/entity.dart'; 6 | import 'package:google_tasks/model/service/authenticator/authenticator.dart'; 7 | import 'package:google_tasks/model/service/user_observer.dart'; 8 | import 'package:meta/meta.dart'; 9 | import 'package:mono_kit/mono_kit.dart'; 10 | 11 | class AccountNotifier extends ChangeNotifier with SubscriptionHolderMixin { 12 | AccountNotifier({ 13 | @required this.authenticator, 14 | @required this.userObserver, 15 | }) { 16 | subscriptionHolder.add( 17 | _auth.onAuthStateChanged.listen((firUser) { 18 | final uid = firUser?.uid; 19 | final uidChanged = _firUser?.uid != uid; 20 | _firUser = firUser; 21 | if (uidChanged) { 22 | if (firUser == null) { 23 | _userDoc = null; 24 | } else { 25 | _userObserveSubscription?.cancel(); 26 | _userObserveSubscription = userObserver.doc(uid).listen((userDoc) { 27 | _userDoc = userDoc; 28 | notifyListeners(); 29 | if (userDoc == null) { 30 | UsersRef.ref().docRef(uid).setData({}); 31 | } 32 | }); 33 | } 34 | } 35 | }), 36 | ); 37 | } 38 | 39 | final Authenticator authenticator; 40 | final UserObserver userObserver; 41 | final _auth = FirebaseAuth.instance; 42 | FirebaseUser _firUser; 43 | UserDoc _userDoc; 44 | StreamSubscription _userObserveSubscription; 45 | 46 | UserDoc get userDoc => _userDoc; 47 | FirebaseUser get firUser => _firUser; 48 | 49 | @override 50 | void dispose() { 51 | _userObserveSubscription?.cancel(); 52 | 53 | super.dispose(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /flutter/lib/model/notifier/notifier.dart: -------------------------------------------------------------------------------- 1 | export 'account_notifier.dart'; 2 | -------------------------------------------------------------------------------- /flutter/lib/model/service/authenticator/auth_error_codes.dart: -------------------------------------------------------------------------------- 1 | class AuthErrorCodes { 2 | static const emailAlreadyInUse = 'ERROR_EMAIL_ALREADY_IN_USE'; 3 | static const providerAlreadyLinked = 'ERROR_PROVIDER_ALREADY_LINKED'; 4 | static const credentialAlreadyInUser = 'ERROR_CREDENTIAL_ALREADY_IN_USE'; 5 | static const requiresRecentLogin = 'ERROR_REQUIRES_RECENT_LOGIN'; 6 | static const accountExistsWithDifferentCredential = 7 | 'ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL'; 8 | static const weakPassword = 'ERROR_WEAK_PASSWORD'; 9 | static const wrongPassword = 'ERROR_WRONG_PASSWORD'; 10 | static const tooManyRequests = 'ERROR_TOO_MANY_REQUESTS'; 11 | static const userDisabled = 'ERROR_USER_DISABLED'; 12 | } 13 | -------------------------------------------------------------------------------- /flutter/lib/model/service/authenticator/authenticator.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:google_sign_in/google_sign_in.dart'; 3 | import 'package:google_tasks/util/util.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | import '../../model.dart'; 7 | 8 | export 'auth_error_codes.dart'; 9 | 10 | class Authenticator implements Disposable { 11 | Authenticator() { 12 | _auth.onAuthStateChanged.pipe(_firUser); 13 | _firUser.distinct((a, b) => a?.uid == b?.uid).map((firUser) { 14 | if (firUser == null) { 15 | return null; 16 | } 17 | return UsersRef.ref().docRef(firUser.uid); 18 | }).pipe(_userRef); 19 | } 20 | 21 | final _auth = FirebaseAuth.instance; 22 | final _googleSignIn = GoogleSignIn(scopes: [ 23 | 'email', 24 | 'https://www.googleapis.com/auth/contacts.readonly', 25 | ]); 26 | final _firUser = BehaviorSubject(); 27 | final _userRef = BehaviorSubject(); 28 | 29 | Future fetchFirUser() => _auth.currentUser(); 30 | ValueStream get firUser => _firUser; 31 | ValueStream get userRef => _userRef; 32 | 33 | Future signIn() async { 34 | final current = await _auth.currentUser(); 35 | if (current != null) { 36 | logger.shout( 37 | 'signIn should be called only if signed out(current: $current).'); 38 | return current; 39 | } 40 | final gAccount = await _googleSignIn.signIn(); 41 | if (gAccount == null) { 42 | return null; 43 | } 44 | final gAuth = await gAccount.authentication; 45 | final authResult = await _auth.signInWithCredential( 46 | GoogleAuthProvider.getCredential( 47 | idToken: gAuth.idToken, 48 | accessToken: gAuth.accessToken, 49 | ), 50 | ); 51 | logger.info(authResult); 52 | return authResult?.user; 53 | } 54 | 55 | Future signOut() async { 56 | await _auth.signOut(); 57 | // await _googleSignIn.signOut(); 58 | } 59 | 60 | @override 61 | void dispose() { 62 | _firUser.close(); 63 | _userRef.close(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /flutter/lib/model/service/service.dart: -------------------------------------------------------------------------------- 1 | export 'authenticator/authenticator.dart'; 2 | export 'tasks_service.dart'; 3 | export 'user_observer.dart'; 4 | -------------------------------------------------------------------------------- /flutter/lib/model/service/tasks_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:google_tasks/util/util.dart'; 6 | import 'package:mono_kit/mono_kit.dart'; 7 | 8 | import '../model.dart'; 9 | 10 | class TasksService with SubscriptionHolderMixin { 11 | TasksService({@required this.authenticator}) { 12 | _tasksRef = authenticator.userRef.value.tasksRef; 13 | subscriptionHolder.add( 14 | authenticator.userRef.listen((userRef) { 15 | _tasksRef = userRef?.tasksRef; 16 | }), 17 | ); 18 | } 19 | 20 | TasksRef _tasksRef; 21 | final _deleted = StreamController.broadcast(); 22 | final Authenticator authenticator; 23 | 24 | void updateFirebaseUser(FirebaseUser firUser) { 25 | logger.info(firUser); 26 | _tasksRef = 27 | firUser == null ? null : UsersRef.ref().docRef(firUser.uid).tasksRef; 28 | } 29 | 30 | Stream> docs() => _tasksRef.documents((ref) => ref.orderBy( 31 | TimestampField.createdAt, 32 | descending: true, 33 | )); 34 | Stream doc(String id) => _tasksRef.docRef(id).document(); 35 | Stream get deleted => _deleted.stream; 36 | 37 | void add(Task task, {String id}) { 38 | _tasksRef.docRef(id).set(task); 39 | } 40 | 41 | void delete(TaskDoc doc) { 42 | _tasksRef.docRef(doc.id).delete(); 43 | _deleted.add(doc); 44 | } 45 | 46 | @override 47 | void dispose() { 48 | _deleted.close(); 49 | 50 | super.dispose(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /flutter/lib/model/service/user_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:google_tasks/model/entity/entity.dart'; 2 | 3 | class UserObserver { 4 | Stream doc(String id) => UsersRef.ref().docRef(id).document(); 5 | } 6 | -------------------------------------------------------------------------------- /flutter/lib/pages/root_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:mono_kit/mono_kit.dart'; 3 | 4 | class RootPage extends StatelessWidget { 5 | const RootPage({Key key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return const Scaffold( 10 | body: TimeoutSwitcher( 11 | timeout: Duration(milliseconds: 500), 12 | timedOutChild: Center( 13 | child: CircularProgressIndicator(), 14 | ), 15 | ), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/input_sheet/due_date_time_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/model/entity/entity.dart'; 3 | import 'package:google_tasks/model/model.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | import 'model.dart'; 7 | 8 | class _DateModel extends ValueNotifier { 9 | _DateModel(Due value) : super(value); 10 | } 11 | 12 | class DueDateTimeDialog extends StatelessWidget { 13 | const DueDateTimeDialog._({Key key}) : super(key: key); 14 | 15 | static Widget wrapped({@required Model model}) { 16 | return MultiProvider( 17 | providers: [ 18 | ChangeNotifierProvider.value(value: model), 19 | ChangeNotifierProvider<_DateModel>(create: (context) { 20 | final model = Provider.of(context, listen: false); 21 | return _DateModel(model.task.due ?? Due(DateTime.now())); 22 | }), 23 | ], 24 | child: const DueDateTimeDialog._(), 25 | ); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Dialog( 31 | child: Column( 32 | mainAxisSize: MainAxisSize.min, 33 | children: [ 34 | // キーボード表示中の遷移で高さが足りずにエラーが出るのを回避 35 | Flexible( 36 | child: SingleChildScrollView( 37 | physics: const AlwaysScrollableScrollPhysics(), 38 | child: Column( 39 | mainAxisSize: MainAxisSize.min, 40 | children: const [ 41 | SizedBox(height: 16), 42 | _Picker(), 43 | _StartTimeTile(), 44 | _RepeatTile(), 45 | ], 46 | ), 47 | ), 48 | ), 49 | const ButtonBar( 50 | children: [ 51 | _CancelButton(), 52 | _DoneButton(), 53 | ], 54 | ), 55 | ], 56 | ), 57 | ); 58 | } 59 | } 60 | 61 | class _RepeatTile extends StatelessWidget { 62 | const _RepeatTile({Key key}) : super(key: key); 63 | @override 64 | Widget build(BuildContext context) { 65 | return ListTile( 66 | leading: Icon(Icons.repeat), 67 | title: const Text('Does not repeat'), 68 | dense: true, 69 | trailing: Icon(Icons.chevron_right), 70 | onTap: () {}, 71 | ); 72 | } 73 | } 74 | 75 | class _CancelButton extends StatelessWidget { 76 | const _CancelButton({Key key}) : super(key: key); 77 | @override 78 | Widget build(BuildContext context) { 79 | return FlatButton( 80 | child: Text(MaterialLocalizations.of(context).cancelButtonLabel), 81 | onPressed: () { 82 | Navigator.of(context).pop(); 83 | }, 84 | ); 85 | } 86 | } 87 | 88 | class _StartTimeTile extends StatelessWidget { 89 | const _StartTimeTile({Key key}) : super(key: key); 90 | @override 91 | Widget build(BuildContext context) { 92 | final dateModel = Provider.of<_DateModel>(context); 93 | return ListTile( 94 | leading: Icon(Icons.access_time), 95 | title: Text(dateModel.value.includeTime 96 | ? dateModel.value.dateTime.toString() 97 | : 'Set start time'), 98 | dense: true, 99 | onTap: () async { 100 | final time = await showTimePicker( 101 | context: context, 102 | initialTime: TimeOfDay.now(), 103 | ); 104 | if (time == null) { 105 | return; 106 | } 107 | final dueTime = dateModel.value.dateTime; 108 | dateModel.value = Due( 109 | DateTime( 110 | dueTime.year, 111 | dueTime.month, 112 | dueTime.day, 113 | time.hour, 114 | time.minute, 115 | ), 116 | includeTime: true, 117 | ); 118 | }, 119 | ); 120 | } 121 | } 122 | 123 | class _DoneButton extends StatelessWidget { 124 | const _DoneButton({Key key}) : super(key: key); 125 | @override 126 | Widget build(BuildContext context) { 127 | final model = Provider.of(context, listen: false); 128 | final task = model.task; 129 | final dateModel = Provider.of<_DateModel>(context, listen: false); 130 | return FlatButton( 131 | child: const Text('DONE'), 132 | onPressed: () { 133 | final due = dateModel.value; 134 | model.task = task.copyWith( 135 | due: due ?? task.due ?? Due(null, includeTime: false), 136 | ); 137 | Navigator.of(context).pop(); 138 | }, 139 | ); 140 | } 141 | } 142 | 143 | class _Picker extends StatelessWidget { 144 | const _Picker({Key key}) : super(key: key); 145 | @override 146 | Widget build(BuildContext context) { 147 | final model = Provider.of<_DateModel>(context); 148 | return CalendarDatePicker( 149 | initialDate: model.value.dateTime, 150 | onDateChanged: (date) => model.value = model.value.copyWith( 151 | dateTime: date, 152 | ), 153 | firstDate: DateTime.now().subtract(const Duration(days: 365)), 154 | lastDate: DateTime.now().add(const Duration(days: 3650)), 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/input_sheet/input_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/widgets/widgets.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'due_date_time_dialog.dart'; 6 | import 'model.dart'; 7 | 8 | class InputSheet extends StatelessWidget { 9 | const InputSheet._({Key key}) : super(key: key); 10 | 11 | static Widget wrapped() { 12 | return ChangeNotifierProvider( 13 | create: (context) => Model( 14 | service: Provider.of(context, listen: false), 15 | ), 16 | child: const InputSheet._(), 17 | ); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Padding( 23 | padding: MediaQuery.of(context).viewInsets, 24 | child: LayoutBuilder( 25 | builder: (context, constraints) { 26 | return ConstrainedBox( 27 | constraints: BoxConstraints(maxHeight: constraints.maxHeight - 44), 28 | child: Padding( 29 | padding: const EdgeInsets.all(8), 30 | child: Column( 31 | mainAxisSize: MainAxisSize.min, 32 | crossAxisAlignment: CrossAxisAlignment.start, 33 | children: [ 34 | const _TitleTextField(), 35 | const Flexible(child: _DetailsTextField()), 36 | const _DueButton(), 37 | IconTheme( 38 | data: Theme.of(context).accentIconTheme, 39 | child: Row( 40 | children: const [ 41 | _DetailButton(), 42 | _DueDateButton(), 43 | Spacer(), 44 | _SaveButton(), 45 | ], 46 | ), 47 | ) 48 | ], 49 | ), 50 | ), 51 | ); 52 | }, 53 | ), 54 | ); 55 | } 56 | } 57 | 58 | class _DueDateButton extends StatelessWidget { 59 | const _DueDateButton({Key key}) : super(key: key); 60 | @override 61 | Widget build(BuildContext context) { 62 | return IconButton( 63 | icon: Icon(Icons.calendar_today), 64 | padding: const EdgeInsets.all(16), 65 | onPressed: () => _showDueDateTimeDialog(context), 66 | ); 67 | } 68 | } 69 | 70 | class _TitleTextField extends StatelessWidget { 71 | const _TitleTextField({Key key}) : super(key: key); 72 | @override 73 | Widget build(BuildContext context) { 74 | final model = Provider.of(context, listen: false); 75 | return Padding( 76 | padding: const EdgeInsets.symmetric(horizontal: 16), 77 | child: TextField( 78 | autofocus: true, 79 | controller: model.titleController, 80 | focusNode: model.titleFocusNode, 81 | decoration: InputDecoration( 82 | hintText: 'New task', 83 | border: InputBorder.none, 84 | ), 85 | ), 86 | ); 87 | } 88 | } 89 | 90 | class _DetailsTextField extends StatelessWidget { 91 | const _DetailsTextField({Key key}) : super(key: key); 92 | @override 93 | Widget build(BuildContext context) { 94 | final model = Provider.of(context); 95 | if (!model.isDetailsShown) { 96 | return const SizedBox(); 97 | } 98 | return Padding( 99 | padding: const EdgeInsets.symmetric(horizontal: 16), 100 | child: TextField( 101 | controller: model.detailsController, 102 | focusNode: model.detailsFocusNode, 103 | decoration: InputDecoration( 104 | hintText: 'Add details', 105 | border: InputBorder.none, 106 | ), 107 | style: Theme.of(context).textTheme.caption, 108 | minLines: 1, 109 | maxLines: null, 110 | ), 111 | ); 112 | } 113 | } 114 | 115 | class _DueButton extends StatelessWidget { 116 | const _DueButton({Key key}) : super(key: key); 117 | @override 118 | Widget build(BuildContext context) { 119 | final model = Provider.of(context); 120 | final due = model.task.due; 121 | 122 | if (model.task.due == null) { 123 | return const SizedBox(); 124 | } 125 | return Padding( 126 | padding: const EdgeInsets.symmetric(horizontal: 16), 127 | child: DueButton( 128 | due: due, 129 | onPressed: () => _showDueDateTimeDialog(context), 130 | onClosePressed: () => model.task = model.task.copyWith( 131 | due: null, 132 | ), 133 | ), 134 | ); 135 | } 136 | } 137 | 138 | class _DetailButton extends StatelessWidget { 139 | const _DetailButton({Key key}) : super(key: key); 140 | @override 141 | Widget build(BuildContext context) { 142 | final model = Provider.of(context); 143 | return IconButton( 144 | icon: Icon(Icons.format_align_left), 145 | padding: const EdgeInsets.all(16), 146 | onPressed: model.isDetailsShown 147 | ? null 148 | : () { 149 | model.showDetails(); 150 | WidgetsBinding.instance.addPostFrameCallback((_) { 151 | model.focusToDetails(context); 152 | }); 153 | }, 154 | ); 155 | } 156 | } 157 | 158 | class _SaveButton extends StatelessWidget { 159 | const _SaveButton({Key key}) : super(key: key); 160 | @override 161 | Widget build(BuildContext context) { 162 | final model = Provider.of(context, listen: false); 163 | return FlatButton( 164 | textTheme: ButtonTextTheme.accent, 165 | // TODO(mono): localize 166 | child: const Text('Save'), 167 | onPressed: () { 168 | model 169 | ..task = model.task.copyWith( 170 | title: model.titleController.text, 171 | details: model.detailsController.text, 172 | ) 173 | ..save(); 174 | Navigator.of(context).pop(); 175 | }, 176 | ); 177 | } 178 | } 179 | 180 | Future _showDueDateTimeDialog(BuildContext context) async { 181 | final model = Provider.of(context, listen: false)..saveFocus(); 182 | await showDialog( 183 | context: context, 184 | builder: (context) => DueDateTimeDialog.wrapped( 185 | model: model, 186 | ), 187 | ); 188 | model.focusToLastIfPossible(context); 189 | } 190 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/input_sheet/model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:google_tasks/model/model.dart'; 4 | 5 | class Model extends ChangeNotifier { 6 | Model({ 7 | @required this.service, 8 | }); 9 | 10 | final TasksService service; 11 | final TextEditingController titleController = TextEditingController(); 12 | final TextEditingController detailsController = TextEditingController(); 13 | final FocusNode titleFocusNode = FocusNode(); 14 | final FocusNode detailsFocusNode = FocusNode(); 15 | FocusNode lastFocusNode; 16 | 17 | var _task = const Task(); 18 | var _isDetailsShown = false; 19 | 20 | Task get task => _task; 21 | bool get isDetailsShown => _isDetailsShown; 22 | 23 | void showDetails() { 24 | _isDetailsShown = true; 25 | notifyListeners(); 26 | } 27 | 28 | set task(Task task) { 29 | _task = task; 30 | notifyListeners(); 31 | } 32 | 33 | void save() { 34 | service.add(task); 35 | } 36 | 37 | void focusToTitle(BuildContext context) { 38 | _focus(context, node: titleFocusNode); 39 | } 40 | 41 | void focusToDetails(BuildContext context) { 42 | _focus(context, node: detailsFocusNode); 43 | } 44 | 45 | void focusToLastIfPossible(BuildContext context) { 46 | if (lastFocusNode != null) { 47 | _focus(context, node: lastFocusNode); 48 | } 49 | } 50 | 51 | void saveFocus() { 52 | for (final node in [titleFocusNode, detailsFocusNode]) { 53 | if (node.hasFocus) { 54 | lastFocusNode = node; 55 | break; 56 | } 57 | } 58 | } 59 | 60 | void _focus( 61 | BuildContext context, { 62 | @required FocusNode node, 63 | }) { 64 | FocusScope.of(context).requestFocus(node); 65 | } 66 | 67 | @override 68 | void dispose() { 69 | titleController.dispose(); 70 | detailsController.dispose(); 71 | 72 | super.dispose(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/setting_sheet/model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:google_tasks/model/service/service.dart'; 3 | 4 | class Model extends ChangeNotifier { 5 | Model({@required this.authenticator}); 6 | final Authenticator authenticator; 7 | 8 | var _status = Status.signedIn; 9 | 10 | Status get status => _status; 11 | 12 | Future signOut() async { 13 | _updateStatus(Status.inProgress); 14 | await authenticator.signOut(); 15 | _updateStatus(Status.signedOut); 16 | } 17 | 18 | void _updateStatus(Status status) { 19 | _status = status; 20 | notifyListeners(); 21 | } 22 | } 23 | 24 | enum Status { 25 | signedIn, 26 | inProgress, 27 | signedOut, 28 | } 29 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/setting_sheet/setting_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:google_tasks/model/notifier/notifier.dart'; 4 | import 'package:mono_kit/mono_kit.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | import 'model.dart'; 8 | 9 | class SettingSheet extends StatelessWidget { 10 | const SettingSheet._({ 11 | Key key, 12 | }) : super(key: key); 13 | 14 | static Widget wrapped() { 15 | return ChangeNotifierProvider( 16 | create: (context) => Model( 17 | authenticator: Provider.of(context, listen: false), 18 | ), 19 | child: const SettingSheet._(), 20 | ); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final model = Provider.of(context); 26 | final account = Provider.of(context); 27 | final firUser = account.firUser; 28 | if (firUser == null) { 29 | return const SizedBox(); 30 | } 31 | return Barrier( 32 | showProgress: ValueNotifier(model.status == Status.inProgress), 33 | child: SafeArea( 34 | child: Column( 35 | mainAxisSize: MainAxisSize.min, 36 | children: [ 37 | Padding( 38 | padding: const EdgeInsets.all(16), 39 | child: Row( 40 | children: [ 41 | CircleAvatar( 42 | backgroundImage: CachedNetworkImageProvider( 43 | firUser.photoUrl, 44 | ), 45 | radius: 18, 46 | ), 47 | const SizedBox(width: 8), 48 | Column( 49 | crossAxisAlignment: CrossAxisAlignment.start, 50 | children: [ 51 | Text( 52 | firUser.displayName, 53 | style: Theme.of(context).textTheme.bodyText2.copyWith( 54 | fontWeight: FontWeight.w600, 55 | ), 56 | ), 57 | const SizedBox(height: 2), 58 | Text( 59 | firUser.email, 60 | style: Theme.of(context).textTheme.caption, 61 | ), 62 | ], 63 | ), 64 | ], 65 | ), 66 | ), 67 | const Divider(), 68 | ListTile( 69 | onTap: model.signOut, 70 | title: const Text('Sign out'), 71 | leading: Icon(Icons.exit_to_app), 72 | ), 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/task_detail_page/task_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/pages/tasks_page/task_tile/task_model.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | class TaskDetailPage extends StatelessWidget { 6 | const TaskDetailPage._({Key key}) : super(key: key); 7 | 8 | static Widget wrapped(Object arguments) { 9 | final model = convertToModelFromArguments(arguments); 10 | return ChangeNotifierProvider.value( 11 | value: model, 12 | child: const TaskDetailPage._(), 13 | ); 14 | } 15 | 16 | static TaskModel convertToModelFromArguments(Object arguments) { 17 | return arguments as TaskModel; 18 | } 19 | 20 | static const routeName = 'Tasks/'; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | actions: [ 27 | IconButton( 28 | icon: Icon(Icons.delete_outline), 29 | onPressed: () async { 30 | final model = Provider.of(context, listen: false); 31 | Navigator.of(context).pop(); 32 | await Future.delayed(const Duration(milliseconds: 500)); 33 | model.delete(); 34 | }, 35 | ) 36 | ], 37 | ), 38 | body: ListView( 39 | children: [ 40 | TextField( 41 | controller: TextEditingController( 42 | text: Provider.of(context).doc.entity.title), 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/task_tile/task_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/model/entity/entity.dart'; 3 | import 'package:google_tasks/model/model.dart'; 4 | import 'package:mono_kit/mono_kit.dart'; 5 | 6 | class TaskModel extends ChangeNotifier with SubscriptionHolderMixin { 7 | TaskModel({ 8 | @required this.doc, 9 | @required this.service, 10 | }) { 11 | subscriptionHolder.add( 12 | service.doc(doc.id).listen((doc) { 13 | final changed = this.doc?.entity != doc?.entity; 14 | this.doc = doc; 15 | if (changed) { 16 | notifyListeners(); 17 | } 18 | }), 19 | ); 20 | } 21 | 22 | TaskDoc doc; 23 | final TasksService service; 24 | 25 | void delete() { 26 | service.delete(doc); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/task_tile/task_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/model/entity/entity.dart'; 3 | import 'package:google_tasks/model/model.dart'; 4 | import 'package:google_tasks/pages/tasks_page/task_detail_page/task_detail_page.dart'; 5 | import 'package:google_tasks/widgets/widgets.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | import 'task_model.dart'; 9 | 10 | class TaskTile extends StatelessWidget { 11 | const TaskTile._({Key key}) : super(key: key); 12 | 13 | static Widget wrapped(TaskDoc doc) { 14 | return ChangeNotifierProvider( 15 | key: ValueKey(doc.id), 16 | create: (context) => TaskModel( 17 | doc: doc, 18 | service: Provider.of(context, listen: false), 19 | ), 20 | child: const TaskTile._(), 21 | ); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | final model = Provider.of(context); 27 | final doc = model.doc; 28 | final task = doc.entity; 29 | return Container( 30 | decoration: BoxDecoration( 31 | border: Border( 32 | bottom: BorderSide( 33 | color: Theme.of(context).dividerColor, 34 | width: 1 / MediaQuery.of(context).devicePixelRatio, 35 | ), 36 | ), 37 | ), 38 | child: InkWell( 39 | onTap: () { 40 | Navigator.of(context) 41 | .pushNamed(TaskDetailPage.routeName, arguments: model); 42 | }, 43 | child: Row( 44 | children: [ 45 | IconButton( 46 | icon: Icon(Icons.radio_button_unchecked), 47 | onPressed: () {}, 48 | ), 49 | Padding( 50 | padding: const EdgeInsets.symmetric(vertical: 16), 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | children: [ 54 | Text(task.title), 55 | if ((task.details ?? '').isNotEmpty) ...[ 56 | const SizedBox(height: 2), 57 | Text( 58 | task.details, 59 | style: Theme.of(context).textTheme.caption, 60 | ), 61 | ], 62 | if (task.due != null) 63 | DueButton( 64 | due: task.due, 65 | onPressed: () {}, 66 | ), 67 | ], 68 | ), 69 | ), 70 | ], 71 | ), 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/tasks_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:google_tasks/l10n/l10n.dart'; 4 | import 'package:google_tasks/model/model.dart'; 5 | import 'package:google_tasks/util/util.dart'; 6 | import 'package:mono_kit/mono_kit.dart'; 7 | 8 | class TasksModel extends ChangeNotifier with SubscriptionHolderMixin { 9 | TasksModel({ 10 | @required this.service, 11 | @required this.l10n, 12 | }) { 13 | subscriptionHolder 14 | ..add( 15 | service.docs().listen( 16 | (tasks) { 17 | _tasks = tasks; 18 | notifyListeners(); 19 | }, 20 | onError: logger.severe, 21 | ), 22 | ) 23 | ..add( 24 | service.deleted.listen((doc) { 25 | _feedback.showUndo( 26 | text: l10n.taskDeleted, 27 | onUndo: () { 28 | service.add(doc.entity, id: doc.id); 29 | }, 30 | ); 31 | }), 32 | ); 33 | } 34 | final TasksService service; 35 | final L10n l10n; 36 | final GlobalKey scaffoldKey = GlobalKey(); 37 | AppFeedback get _feedback => AppFeedback(scaffoldKey); 38 | var _tasks = []; 39 | 40 | List get tasks => List.unmodifiable(_tasks); 41 | } 42 | -------------------------------------------------------------------------------- /flutter/lib/pages/tasks_page/tasks_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/l10n/l10n.dart'; 3 | import 'package:google_tasks/pages/tasks_page/task_tile/task_tile.dart'; 4 | import 'package:google_tasks/util/util.dart'; 5 | import 'package:google_tasks/widgets/widgets.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | import 'input_sheet/input_sheet.dart'; 9 | import 'setting_sheet/setting_sheet.dart'; 10 | import 'tasks_model.dart'; 11 | 12 | class TasksPage extends StatelessWidget { 13 | const TasksPage._({Key key}) : super(key: key); 14 | 15 | static const routeName = 'Tasks'; 16 | 17 | static Widget wrapped(BuildContext context) { 18 | return ChangeNotifierProvider( 19 | create: (_context) => TasksModel( 20 | service: Provider.of(context, listen: false), 21 | l10n: L10n.of(context), 22 | ), 23 | child: const TasksPage._(), 24 | ); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | key: Provider.of(context, listen: false).scaffoldKey, 31 | resizeToAvoidBottomInset: false, 32 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 33 | floatingActionButton: _buildFab(context), 34 | bottomNavigationBar: _buildBottomNavigationBar(context), 35 | body: const _Body(), 36 | ); 37 | } 38 | 39 | Widget _buildFab(BuildContext context) { 40 | return FloatingActionButton( 41 | onPressed: () { 42 | showModalBottomSheet( 43 | isScrollControlled: true, 44 | context: context, 45 | builder: (context) => SafeArea(child: InputSheet.wrapped()), 46 | ); 47 | }, 48 | child: const GoogleAdd(), 49 | ); 50 | } 51 | 52 | Widget _buildBottomNavigationBar(BuildContext context) { 53 | return Container( 54 | decoration: BoxDecoration( 55 | boxShadow: [ 56 | BoxShadow( 57 | color: Colors.black.withOpacity(0.2), 58 | offset: Offset.zero, 59 | blurRadius: 8, 60 | ) 61 | ], 62 | ), 63 | child: BottomAppBar( 64 | child: Row( 65 | mainAxisSize: MainAxisSize.max, 66 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 67 | children: [ 68 | IconButton( 69 | icon: Icon(Icons.menu), 70 | onPressed: () { 71 | showModalBottomSheet( 72 | context: context, 73 | builder: (context) => SettingSheet.wrapped(), 74 | ); 75 | }, 76 | ), 77 | IconButton( 78 | icon: Icon(Icons.more_horiz), 79 | onPressed: () {}, 80 | ), 81 | ], 82 | ), 83 | ), 84 | ); 85 | } 86 | } 87 | 88 | class _Body extends StatelessWidget { 89 | const _Body({Key key}) : super(key: key); 90 | @override 91 | Widget build(BuildContext context) { 92 | final model = Provider.of(context); 93 | final docs = model.tasks; 94 | logger.info('docs count: ${docs.length}'); 95 | return SafeArea( 96 | child: CustomScrollView( 97 | slivers: [ 98 | SliverToBoxAdapter( 99 | child: Padding( 100 | padding: const EdgeInsets.symmetric( 101 | horizontal: 48, 102 | vertical: 16, 103 | ), 104 | child: Text( 105 | 'My Tasks', 106 | style: Theme.of(context).textTheme.headline5, 107 | ), 108 | ), 109 | ), 110 | SliverList( 111 | delegate: SliverChildBuilderDelegate( 112 | (context, index) { 113 | final doc = docs[index]; 114 | return TaskTile.wrapped(doc); 115 | }, 116 | childCount: docs.length, 117 | ), 118 | ) 119 | ], 120 | ), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /flutter/lib/pages/welcome_page/model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:google_tasks/model/service/service.dart'; 3 | 4 | class Model extends ChangeNotifier { 5 | Model({ 6 | @required this.authenticator, 7 | }); 8 | final Authenticator authenticator; 9 | var _status = Status.loggedOut; 10 | 11 | Status get status => _status; 12 | 13 | Future signIn() async { 14 | _updateStatus(Status.inProgress); 15 | final firUser = await authenticator.signIn(); 16 | _updateStatus(firUser == null ? Status.loggedOut : Status.loggedIn); 17 | } 18 | 19 | void _updateStatus(Status status) { 20 | _status = status; 21 | notifyListeners(); 22 | } 23 | } 24 | 25 | enum Status { 26 | loggedOut, 27 | inProgress, 28 | loggedIn, 29 | } 30 | -------------------------------------------------------------------------------- /flutter/lib/pages/welcome_page/welcome_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/l10n/l10n.dart'; 3 | import 'package:mono_kit/mono_kit.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:undraw/undraw.dart'; 6 | 7 | import 'model.dart'; 8 | 9 | class WelcomePage extends StatelessWidget { 10 | const WelcomePage._({Key key}) : super(key: key); 11 | 12 | static Widget wrapped() { 13 | return ChangeNotifierProvider( 14 | create: (context) => Model( 15 | authenticator: Provider.of(context, listen: false), 16 | ), 17 | child: const WelcomePage._(), 18 | ); 19 | } 20 | 21 | static const routeName = '/Welcome'; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final model = Provider.of(context); 26 | final width = MediaQuery.of(context).size.width; 27 | return Barrier.value( 28 | showProgress: model.status == Status.inProgress, 29 | child: Scaffold( 30 | body: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | FittedBox( 34 | child: UnDraw( 35 | color: Theme.of(context).accentColor, 36 | illustration: UnDrawIllustration.complete_task, 37 | placeholder: SizedBox( 38 | height: width * 0.7, 39 | width: width, 40 | ), 41 | ), 42 | ), 43 | const SizedBox(height: 32), 44 | Text( 45 | L10n.of(context).welcomeTitle, 46 | style: Theme.of(context).textTheme.headline5, 47 | ), 48 | const SizedBox(height: 16), 49 | const Text( 50 | 'Keep track of important things you need to get done\n' 51 | 'in one place.', 52 | textAlign: TextAlign.center, 53 | ), 54 | const SizedBox(height: 12), 55 | RaisedButton( 56 | child: const Text('Get started'), 57 | onPressed: () => 58 | Provider.of(context, listen: false).signIn(), 59 | elevation: 0, 60 | padding: const EdgeInsets.symmetric(horizontal: 32), 61 | textTheme: ButtonTextTheme.primary, 62 | ) 63 | ], 64 | ), 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /flutter/lib/router.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/pages/tasks_page/tasks_page.dart'; 3 | import 'package:google_tasks/pages/welcome_page/welcome_page.dart'; 4 | import 'package:google_tasks/util/util.dart'; 5 | import 'package:mono_kit/mono_kit.dart'; 6 | 7 | import 'model/model.dart'; 8 | import 'pages/root_page.dart'; 9 | import 'pages/tasks_page/task_detail_page/task_detail_page.dart'; 10 | 11 | typedef WidgetPageBuilder = Widget Function( 12 | BuildContext context, 13 | RouteSettings settings, 14 | ); 15 | 16 | class Router with SubscriptionHolderMixin { 17 | Router({ 18 | @required this.authenticator, 19 | @required this.navigatorKey, 20 | }) { 21 | _handleRootPage(); 22 | } 23 | 24 | final Authenticator authenticator; 25 | final GlobalKey navigatorKey; 26 | NavigatorState get _navigator => navigatorKey.currentState; 27 | 28 | void _handleRootPage() { 29 | subscriptionHolder.add( 30 | authenticator.firUser 31 | .map((firUser) => 32 | firUser == null ? WelcomePage.routeName : TasksPage.routeName) 33 | .distinct((a, b) => a == b) 34 | .listen((routeName) { 35 | _navigator 36 | ..popToRoot() 37 | ..pushReplacementNamed(routeName); 38 | }), 39 | ); 40 | } 41 | 42 | static final _routes = { 43 | '/': (context, settings) => const RootPage(), 44 | TaskDetailPage.routeName: (context, settings) { 45 | return TaskDetailPage.wrapped( 46 | settings.arguments, 47 | ); 48 | }, 49 | }; 50 | final _fadeRoutes = { 51 | TasksPage.routeName: (context, settings) => TasksPage.wrapped(context), 52 | WelcomePage.routeName: (context, settings) => WelcomePage.wrapped(), 53 | }; 54 | 55 | Route onGenerateRoute(RouteSettings _settings) { 56 | var settings = _settings; 57 | logger.info(settings.name); 58 | var pageBuilder = _routes[settings.name]; 59 | if (settings.name == TaskDetailPage.routeName) { 60 | final model = 61 | TaskDetailPage.convertToModelFromArguments(settings.arguments); 62 | settings = settings.copyWith(name: '${settings.name}${model.doc.id}'); 63 | } 64 | if (pageBuilder != null) { 65 | return MaterialPageRoute( 66 | builder: (context) => pageBuilder(context, settings), 67 | settings: settings, 68 | ); 69 | } 70 | pageBuilder = _fadeRoutes[settings.name]; 71 | if (pageBuilder != null) { 72 | return FadePageRoute( 73 | builder: (context) => pageBuilder(context, settings), 74 | settings: settings, 75 | ); 76 | } 77 | 78 | assert(false, 'unexpected settings: $settings'); 79 | return null; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /flutter/lib/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ThemeData buildTheme() { 4 | final base = ThemeData.light(); 5 | const accentColor = Color(0xFF3B72E2); 6 | const backgroundColor = Colors.white; 7 | const labelColor = Colors.black; 8 | const iconColor = Color(0xFF666666); 9 | const fillColor = Color(0xFFF5F5F5); 10 | return base.copyWith( 11 | // primaryColor: backgroundColor, 12 | primaryColorBrightness: Brightness.light, 13 | accentColor: accentColor, 14 | scaffoldBackgroundColor: backgroundColor, 15 | dividerColor: Colors.black26, 16 | buttonColor: accentColor, 17 | colorScheme: base.colorScheme.copyWith( 18 | secondary: backgroundColor, 19 | // onSecondary: labelColor, 20 | ), 21 | accentIconTheme: base.accentIconTheme.copyWith( 22 | color: accentColor, 23 | ), 24 | primaryTextTheme: base.primaryTextTheme.apply( 25 | bodyColor: labelColor, 26 | ), 27 | appBarTheme: base.appBarTheme.copyWith( 28 | color: backgroundColor, 29 | elevation: 0, 30 | brightness: Brightness.light, 31 | ), 32 | bottomAppBarTheme: base.bottomAppBarTheme.copyWith( 33 | elevation: 0, 34 | shape: const CircularNotchedRectangle(), 35 | ), 36 | bottomSheetTheme: base.bottomSheetTheme.copyWith( 37 | // backgroundColor: Colors.yellow, 38 | shape: RoundedRectangleBorder( 39 | borderRadius: BorderRadius.circular(10), 40 | ), 41 | ), 42 | buttonTheme: base.buttonTheme.copyWith( 43 | buttonColor: accentColor, 44 | shape: RoundedRectangleBorder( 45 | borderRadius: BorderRadius.circular(8), 46 | ), 47 | colorScheme: base.buttonTheme.colorScheme.copyWith( 48 | secondary: accentColor, 49 | ), 50 | ), 51 | dialogTheme: base.dialogTheme.copyWith( 52 | shape: RoundedRectangleBorder( 53 | borderRadius: BorderRadius.circular(10), 54 | ), 55 | ), 56 | inputDecorationTheme: const InputDecorationTheme( 57 | fillColor: fillColor, 58 | ), 59 | primaryIconTheme: base.primaryIconTheme.copyWith( 60 | color: iconColor, 61 | ), 62 | snackBarTheme: base.snackBarTheme.copyWith( 63 | behavior: SnackBarBehavior.floating, 64 | ), 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /flutter/lib/util/app_feedback.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/exception/app_exception.dart'; 3 | import 'package:google_tasks/l10n/l10n.dart'; 4 | 5 | import 'util.dart'; 6 | 7 | class AppFeedback { 8 | AppFeedback(this.key); 9 | AppFeedback.fromState(ScaffoldState state) : _state = state; 10 | AppFeedback.fromContext(BuildContext context) 11 | : this.fromState(Scaffold.of(context)); 12 | 13 | GlobalKey key; 14 | ScaffoldState _state; 15 | 16 | ScaffoldState get state => _state ?? key.currentState; 17 | BuildContext get context => state.context; 18 | 19 | void show(String text) { 20 | state 21 | ..removeCurrentSnackBar() 22 | ..showSnackBar( 23 | SnackBar(content: Text(text)), 24 | ); 25 | } 26 | 27 | void showUndo({ 28 | @required String text, 29 | @required VoidCallback onUndo, 30 | }) { 31 | final l10n = L10n.of(context); 32 | state 33 | ..removeCurrentSnackBar() 34 | ..showSnackBar( 35 | SnackBar( 36 | content: Text(text), 37 | action: SnackBarAction( 38 | textColor: Theme.of(context).textSelectionColor, 39 | label: l10n.buttonUndo, 40 | onPressed: onUndo, 41 | ), 42 | ), 43 | ); 44 | } 45 | 46 | void notImplemented() { 47 | showError(AppException.notImplemented); 48 | } 49 | 50 | void showError(dynamic error) { 51 | state 52 | ..removeCurrentSnackBar() 53 | ..showSnackBar( 54 | SnackBar( 55 | backgroundColor: Theme.of(context).errorColor, 56 | content: Text( 57 | localizeError( 58 | error: error, 59 | l10n: L10n.of(context), 60 | ), 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /flutter/lib/util/app_navigator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:google_tasks/l10n/l10n.dart'; 4 | 5 | import 'util.dart'; 6 | 7 | class AppNavigator { 8 | AppNavigator(); 9 | 10 | GlobalKey key = GlobalKey(); 11 | 12 | NavigatorState get navigator => key.currentState; 13 | BuildContext get descendantContext => navigator.overlay.context; 14 | 15 | void popToRouteName(String name) { 16 | navigator.popUntil(ModalRoute.withName(name)); 17 | } 18 | 19 | void popToRoot() { 20 | navigator.popUntil((r) => r.isFirst); 21 | } 22 | 23 | void popToFirstFullscreenDialog() { 24 | navigator.popUntil((r) => (r is PageRoute) && r.fullscreenDialog); 25 | } 26 | 27 | void showErrorDialog(dynamic error) { 28 | final l10n = L10n.of(descendantContext); 29 | showDialog( 30 | context: descendantContext, 31 | builder: (context) => AlertDialog( 32 | title: Text( 33 | l10n.errorOccurred, 34 | style: TextStyle( 35 | color: Theme.of(context).errorColor, 36 | ), 37 | ), 38 | content: Text( 39 | localizeError( 40 | error: error, 41 | l10n: l10n, 42 | ), 43 | ), 44 | actions: [ 45 | FlatButton( 46 | child: Text(MaterialLocalizations.of(context).okButtonLabel), 47 | onPressed: () { 48 | Navigator.of(context).pop(); 49 | }, 50 | ) 51 | ], 52 | ), 53 | ); 54 | } 55 | 56 | void showOkDialog({ 57 | @required String title, 58 | @required String message, 59 | String okLabel, 60 | }) { 61 | showConfirmDialog( 62 | title: title, 63 | message: message, 64 | actions: [ 65 | DialogAction( 66 | label: okLabel ?? 67 | MaterialLocalizations.of(descendantContext).okButtonLabel, 68 | ) 69 | ], 70 | ); 71 | } 72 | 73 | Future showConfirmDialog({ 74 | @required String title, 75 | @required String message, 76 | @required List> actions, 77 | }) { 78 | return showDialog( 79 | context: descendantContext, 80 | builder: (context) => AlertDialog( 81 | title: Text(title), 82 | content: Text(message), 83 | actions: actions 84 | .map( 85 | (a) => FlatButton( 86 | child: Text(a.label), 87 | onPressed: () { 88 | Navigator.of(context).pop(a.key); 89 | }, 90 | ), 91 | ) 92 | .toList(), 93 | ), 94 | ); 95 | } 96 | 97 | Future showConfirmSheet({ 98 | @required String title, 99 | String message, 100 | @required List> actions, 101 | }) { 102 | return showModalBottomSheet( 103 | context: descendantContext, 104 | builder: (context) { 105 | return SafeArea( 106 | child: Column( 107 | mainAxisSize: MainAxisSize.min, 108 | children: [ 109 | if (message == null) 110 | ListTile( 111 | title: Text(title), 112 | dense: true, 113 | ), 114 | if (message != null) ...[ 115 | ListTile( 116 | title: Text(title), 117 | subtitle: Text(message), 118 | ), 119 | const Divider() 120 | ], 121 | ...actions.map( 122 | (a) { 123 | return ListTile( 124 | leading: Icon(a.icon), 125 | title: Text(a.label), 126 | onTap: () => Navigator.of(context).pop(a.key), 127 | ); 128 | }, 129 | ), 130 | ], 131 | ), 132 | ); 133 | }, 134 | ); 135 | } 136 | } 137 | 138 | @immutable 139 | class DialogAction { 140 | const DialogAction({ 141 | @required this.label, 142 | this.key, 143 | }); 144 | 145 | final T key; 146 | final String label; 147 | } 148 | 149 | @immutable 150 | class SheetAction { 151 | const SheetAction({ 152 | @required this.label, 153 | @required this.icon, 154 | this.key, 155 | }); 156 | 157 | final String label; 158 | final IconData icon; 159 | final T key; 160 | } 161 | -------------------------------------------------------------------------------- /flutter/lib/util/functions.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_functions/cloud_functions.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:google_tasks/exception/app_exception.dart'; 5 | import 'package:google_tasks/l10n/l10n.dart'; 6 | import 'package:google_tasks/model/service/service.dart'; 7 | import 'package:google_tasks/util/util.dart'; 8 | import 'package:url_launcher/url_launcher.dart'; 9 | 10 | export 'package:mono_kit/functions/functions.dart'; 11 | 12 | void dismissKeyboard(BuildContext context) { 13 | FocusScope.of(context).requestFocus(FocusNode()); 14 | } 15 | 16 | void copyToClipboard(BuildContext context, String text) { 17 | Clipboard.setData(ClipboardData(text: text)); 18 | AppFeedback.fromContext(context).show(L10n.of(context).copiedToPasteboard); 19 | } 20 | 21 | Future launchOrFallback( 22 | String url, { 23 | @required String fallbackUrl, 24 | }) async { 25 | if (await canLaunch(url)) { 26 | return launch(url); 27 | } else { 28 | await launch(fallbackUrl); 29 | return false; 30 | } 31 | } 32 | 33 | String localizeError({ 34 | @required dynamic error, 35 | @required L10n l10n, 36 | }) { 37 | if (error == null) { 38 | return ''; 39 | } 40 | if (error is PlatformException) { 41 | final code = error.code; 42 | switch (code) { 43 | case AuthErrorCodes.accountExistsWithDifferentCredential: 44 | return l10n.accountExistsWithDifferentCredential; 45 | case AuthErrorCodes.weakPassword: 46 | case AuthErrorCodes.wrongPassword: 47 | return l10n.errorInvalidPassword; 48 | case AuthErrorCodes.tooManyRequests: 49 | return l10n.errorTooManyRequests; 50 | case AuthErrorCodes.requiresRecentLogin: 51 | return l10n.errorRequiresRecentLogin; 52 | case AuthErrorCodes.userDisabled: 53 | return l10n.errorUserDisabled; 54 | } 55 | return error.message; 56 | } 57 | if (error is CloudFunctionsException) { 58 | return error.message; 59 | } 60 | if (error is AppException) { 61 | final code = error.code; 62 | if (code == AppErrorCodes.notImplemented) { 63 | return l10n.notImplemented; 64 | } 65 | } 66 | return error.toString(); 67 | } 68 | -------------------------------------------------------------------------------- /flutter/lib/util/logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:simple_logger/simple_logger.dart'; 2 | 3 | final SimpleLogger logger = SimpleLogger() 4 | ..mode = LoggerMode.print 5 | ..setLevel( 6 | Level.FINEST, 7 | includeCallerInfo: true, 8 | ); 9 | -------------------------------------------------------------------------------- /flutter/lib/util/state_helper_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:google_tasks/l10n/l10n.dart'; 3 | import 'package:mono_kit/mono_kit.dart'; 4 | 5 | import 'util.dart'; 6 | 7 | mixin StateHelperMixin on State { 8 | @protected 9 | final SubscriptionHolder subscriptionHolder = SubscriptionHolder(); 10 | @protected 11 | bool get isCurrentRoute => mounted && ModalRoute.of(context).isCurrent; 12 | @protected 13 | AppFeedback get feedback => AppFeedback.fromContext(context); 14 | @protected 15 | L10n get l10n => L10n.of(context); 16 | 17 | void registerFeedback(Stream stream) { 18 | subscriptionHolder.add( 19 | stream.listen( 20 | (message) { 21 | if (!isCurrentRoute) { 22 | return; 23 | } 24 | feedback.show(message); 25 | }, 26 | onError: (dynamic error) { 27 | if (!isCurrentRoute) { 28 | return; 29 | } 30 | feedback.showError(error); 31 | }, 32 | ), 33 | ); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | subscriptionHolder.dispose(); 39 | super.dispose(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /flutter/lib/util/util.dart: -------------------------------------------------------------------------------- 1 | export 'package:disposable_provider/disposable_provider.dart'; 2 | export 'package:mono_kit/mono_kit.dart'; 3 | export 'package:provider/provider.dart'; 4 | export 'package:route_observer_mixin/route_observer_mixin.dart'; 5 | 6 | export 'app_feedback.dart'; 7 | export 'functions.dart'; 8 | export 'logger.dart'; 9 | export 'state_helper_mixin.dart'; 10 | -------------------------------------------------------------------------------- /flutter/lib/widgets/due_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_tasks/model/model.dart'; 3 | import 'package:intl/intl.dart'; 4 | 5 | class DueButton extends StatelessWidget { 6 | const DueButton({ 7 | Key key, 8 | @required this.due, 9 | @required this.onPressed, 10 | this.onClosePressed, 11 | }) : super(key: key); 12 | 13 | final Due due; 14 | final VoidCallback onPressed; 15 | final VoidCallback onClosePressed; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return RaisedButton( 20 | color: Theme.of(context).inputDecorationTheme.fillColor, 21 | elevation: 0, 22 | highlightElevation: 0, 23 | shape: OutlineInputBorder( 24 | borderSide: BorderSide(color: Theme.of(context).dividerColor), 25 | borderRadius: const BorderRadius.all( 26 | Radius.circular(10), 27 | ), 28 | ), 29 | onPressed: onPressed, 30 | child: Row( 31 | mainAxisAlignment: MainAxisAlignment.start, 32 | mainAxisSize: MainAxisSize.min, 33 | children: [ 34 | Icon( 35 | Icons.calendar_today, 36 | size: 16, 37 | color: Theme.of(context).accentColor, 38 | ), 39 | const SizedBox(width: 8), 40 | Text(DateFormat.MMMEd().format(due.dateTime)), 41 | const SizedBox(width: 8), 42 | if (onClosePressed != null) 43 | SizedBox( 44 | width: 24, 45 | height: 24, 46 | child: IconButton( 47 | icon: Icon(Icons.close), 48 | padding: EdgeInsets.zero, 49 | onPressed: onClosePressed, 50 | ), 51 | ) 52 | ], 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /flutter/lib/widgets/google_add.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GoogleAdd extends StatelessWidget { 4 | const GoogleAdd({Key key, this.size = 24}) : super(key: key); 5 | 6 | final double size; 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return SizedBox( 11 | width: 24, 12 | height: 24, 13 | child: CustomPaint( 14 | painter: _Painter(), 15 | ), 16 | ); 17 | } 18 | } 19 | 20 | class _Painter extends CustomPainter { 21 | final _paintRed = Paint()..color = const Color(0xFFDE3A2F); 22 | final _paintBlue = Paint()..color = const Color(0xFF367BF4); 23 | final _paintGreen = Paint()..color = const Color(0xFF299E49); 24 | final _paintYellow = Paint()..color = const Color(0xFFF9B30B); 25 | 26 | @override 27 | void paint(Canvas canvas, Size size) { 28 | final strokeWidth = size.width / 6; 29 | final width = size.width; 30 | final leading = (width - strokeWidth) / 2; 31 | final trailing = (width + strokeWidth) / 2; 32 | 33 | canvas 34 | ..drawPath( 35 | Path() 36 | ..moveTo(leading, 0) 37 | ..lineTo(trailing, 0) 38 | ..lineTo(trailing, leading) 39 | ..lineTo(leading, trailing) 40 | ..close(), 41 | _paintRed, 42 | ) 43 | ..drawPath( 44 | Path() 45 | ..moveTo(trailing, leading) 46 | ..lineTo(width, leading) 47 | ..lineTo(width, trailing) 48 | ..lineTo(leading, trailing) 49 | ..close(), 50 | _paintBlue, 51 | ) 52 | ..drawPath( 53 | Path() 54 | ..moveTo(leading, trailing) 55 | ..lineTo(trailing, trailing) 56 | ..lineTo(trailing, width) 57 | ..lineTo(leading, width) 58 | ..close(), 59 | _paintGreen, 60 | ) 61 | ..drawPath( 62 | Path() 63 | ..moveTo(0, leading) 64 | ..lineTo(leading, leading) 65 | ..lineTo(leading, trailing) 66 | ..lineTo(0, trailing) 67 | ..close(), 68 | _paintYellow, 69 | ); 70 | } 71 | 72 | @override 73 | bool shouldRepaint(CustomPainter oldDelegate) => false; 74 | } 75 | -------------------------------------------------------------------------------- /flutter/lib/widgets/widgets.dart: -------------------------------------------------------------------------------- 1 | export 'due_button.dart'; 2 | export 'google_add.dart'; 3 | -------------------------------------------------------------------------------- /flutter/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "1.0.3" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "0.39.4" 18 | archive: 19 | dependency: transitive 20 | description: 21 | name: archive 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.11" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.5.2" 32 | async: 33 | dependency: transitive 34 | description: 35 | name: async 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.4.0" 39 | boolean_selector: 40 | dependency: transitive 41 | description: 42 | name: boolean_selector 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.5" 46 | build: 47 | dependency: transitive 48 | description: 49 | name: build 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.2" 53 | build_config: 54 | dependency: transitive 55 | description: 56 | name: build_config 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "0.4.2" 60 | build_daemon: 61 | dependency: transitive 62 | description: 63 | name: build_daemon 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.4" 67 | build_resolvers: 68 | dependency: transitive 69 | description: 70 | name: build_resolvers 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.3.3" 74 | build_runner: 75 | dependency: "direct dev" 76 | description: 77 | name: build_runner 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.8.0" 81 | build_runner_core: 82 | dependency: transitive 83 | description: 84 | name: build_runner_core 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "4.5.3" 88 | built_collection: 89 | dependency: transitive 90 | description: 91 | name: built_collection 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "4.3.2" 95 | built_value: 96 | dependency: transitive 97 | description: 98 | name: built_value 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "7.0.9" 102 | cached_network_image: 103 | dependency: "direct main" 104 | description: 105 | name: cached_network_image 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "2.0.0" 109 | charcode: 110 | dependency: transitive 111 | description: 112 | name: charcode 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.1.2" 116 | checked_yaml: 117 | dependency: transitive 118 | description: 119 | name: checked_yaml 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "1.0.2" 123 | cloud_firestore: 124 | dependency: "direct main" 125 | description: 126 | name: cloud_firestore 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "0.13.4+2" 130 | cloud_firestore_platform_interface: 131 | dependency: transitive 132 | description: 133 | name: cloud_firestore_platform_interface 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "1.1.0" 137 | cloud_firestore_web: 138 | dependency: transitive 139 | description: 140 | name: cloud_firestore_web 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "0.1.1+2" 144 | cloud_functions: 145 | dependency: "direct main" 146 | description: 147 | name: cloud_functions 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "0.4.2+3" 151 | cloud_functions_platform_interface: 152 | dependency: transitive 153 | description: 154 | name: cloud_functions_platform_interface 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.0.0" 158 | cloud_functions_web: 159 | dependency: transitive 160 | description: 161 | name: cloud_functions_web 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "1.0.3" 165 | code_builder: 166 | dependency: transitive 167 | description: 168 | name: code_builder 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "3.2.1" 172 | collection: 173 | dependency: transitive 174 | description: 175 | name: collection 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "1.14.11" 179 | convert: 180 | dependency: transitive 181 | description: 182 | name: convert 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "2.1.1" 186 | crypto: 187 | dependency: transitive 188 | description: 189 | name: crypto 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "2.1.3" 193 | csslib: 194 | dependency: transitive 195 | description: 196 | name: csslib 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "0.16.1" 200 | cupertino_icons: 201 | dependency: "direct main" 202 | description: 203 | name: cupertino_icons 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "0.1.3" 207 | dart_style: 208 | dependency: transitive 209 | description: 210 | name: dart_style 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "1.3.3" 214 | disposable_provider: 215 | dependency: "direct main" 216 | description: 217 | name: disposable_provider 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "2.1.1" 221 | firebase: 222 | dependency: transitive 223 | description: 224 | name: firebase 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "7.2.1" 228 | firebase_analytics: 229 | dependency: "direct main" 230 | description: 231 | name: firebase_analytics 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "5.0.11" 235 | firebase_auth: 236 | dependency: "direct main" 237 | description: 238 | name: firebase_auth 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "0.15.5+3" 242 | firebase_auth_platform_interface: 243 | dependency: transitive 244 | description: 245 | name: firebase_auth_platform_interface 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "1.1.7" 249 | firebase_auth_web: 250 | dependency: transitive 251 | description: 252 | name: firebase_auth_web 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "0.1.2" 256 | firebase_core: 257 | dependency: "direct main" 258 | description: 259 | name: firebase_core 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "0.4.4+3" 263 | firebase_core_platform_interface: 264 | dependency: transitive 265 | description: 266 | name: firebase_core_platform_interface 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "1.0.4" 270 | firebase_core_web: 271 | dependency: transitive 272 | description: 273 | name: firebase_core_web 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "0.1.1+2" 277 | firebase_crashlytics: 278 | dependency: "direct main" 279 | description: 280 | name: firebase_crashlytics 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "0.1.3+3" 284 | firebase_performance: 285 | dependency: "direct main" 286 | description: 287 | name: firebase_performance 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "0.3.1+8" 291 | firestore_ref: 292 | dependency: "direct main" 293 | description: 294 | name: firestore_ref 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "0.8.0-dev+24" 298 | fixnum: 299 | dependency: transitive 300 | description: 301 | name: fixnum 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "0.10.11" 305 | flutter: 306 | dependency: "direct main" 307 | description: flutter 308 | source: sdk 309 | version: "0.0.0" 310 | flutter_cache_manager: 311 | dependency: transitive 312 | description: 313 | name: flutter_cache_manager 314 | url: "https://pub.dartlang.org" 315 | source: hosted 316 | version: "1.1.3" 317 | flutter_localizations: 318 | dependency: "direct main" 319 | description: flutter 320 | source: sdk 321 | version: "0.0.0" 322 | flutter_svg: 323 | dependency: "direct main" 324 | description: 325 | name: flutter_svg 326 | url: "https://pub.dartlang.org" 327 | source: hosted 328 | version: "0.17.3+1" 329 | flutter_test: 330 | dependency: "direct dev" 331 | description: flutter 332 | source: sdk 333 | version: "0.0.0" 334 | flutter_web_plugins: 335 | dependency: transitive 336 | description: flutter 337 | source: sdk 338 | version: "0.0.0" 339 | freezed: 340 | dependency: "direct dev" 341 | description: 342 | name: freezed 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "0.10.4" 346 | freezed_annotation: 347 | dependency: "direct main" 348 | description: 349 | name: freezed_annotation 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "0.7.1" 353 | glob: 354 | dependency: transitive 355 | description: 356 | name: glob 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "1.2.0" 360 | google_sign_in: 361 | dependency: "direct main" 362 | description: 363 | name: google_sign_in 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "4.3.0" 367 | google_sign_in_platform_interface: 368 | dependency: transitive 369 | description: 370 | name: google_sign_in_platform_interface 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "1.1.0" 374 | google_sign_in_web: 375 | dependency: transitive 376 | description: 377 | name: google_sign_in_web 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "0.8.4" 381 | graphs: 382 | dependency: transitive 383 | description: 384 | name: graphs 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "0.2.0" 388 | html: 389 | dependency: transitive 390 | description: 391 | name: html 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "0.14.0+3" 395 | http: 396 | dependency: transitive 397 | description: 398 | name: http 399 | url: "https://pub.dartlang.org" 400 | source: hosted 401 | version: "0.12.0+4" 402 | http_multi_server: 403 | dependency: transitive 404 | description: 405 | name: http_multi_server 406 | url: "https://pub.dartlang.org" 407 | source: hosted 408 | version: "2.2.0" 409 | http_parser: 410 | dependency: transitive 411 | description: 412 | name: http_parser 413 | url: "https://pub.dartlang.org" 414 | source: hosted 415 | version: "3.1.4" 416 | image: 417 | dependency: transitive 418 | description: 419 | name: image 420 | url: "https://pub.dartlang.org" 421 | source: hosted 422 | version: "2.1.4" 423 | intl: 424 | dependency: transitive 425 | description: 426 | name: intl 427 | url: "https://pub.dartlang.org" 428 | source: hosted 429 | version: "0.16.1" 430 | intl_translation: 431 | dependency: "direct dev" 432 | description: 433 | name: intl_translation 434 | url: "https://pub.dartlang.org" 435 | source: hosted 436 | version: "0.17.9" 437 | io: 438 | dependency: transitive 439 | description: 440 | name: io 441 | url: "https://pub.dartlang.org" 442 | source: hosted 443 | version: "0.3.3" 444 | js: 445 | dependency: transitive 446 | description: 447 | name: js 448 | url: "https://pub.dartlang.org" 449 | source: hosted 450 | version: "0.6.1+1" 451 | json_annotation: 452 | dependency: "direct main" 453 | description: 454 | name: json_annotation 455 | url: "https://pub.dartlang.org" 456 | source: hosted 457 | version: "3.0.1" 458 | json_serializable: 459 | dependency: "direct dev" 460 | description: 461 | name: json_serializable 462 | url: "https://pub.dartlang.org" 463 | source: hosted 464 | version: "3.3.0" 465 | logging: 466 | dependency: transitive 467 | description: 468 | name: logging 469 | url: "https://pub.dartlang.org" 470 | source: hosted 471 | version: "0.11.4" 472 | matcher: 473 | dependency: transitive 474 | description: 475 | name: matcher 476 | url: "https://pub.dartlang.org" 477 | source: hosted 478 | version: "0.12.6" 479 | meta: 480 | dependency: transitive 481 | description: 482 | name: meta 483 | url: "https://pub.dartlang.org" 484 | source: hosted 485 | version: "1.1.8" 486 | mime: 487 | dependency: transitive 488 | description: 489 | name: mime 490 | url: "https://pub.dartlang.org" 491 | source: hosted 492 | version: "0.9.6+3" 493 | mono_kit: 494 | dependency: "direct main" 495 | description: 496 | name: mono_kit 497 | url: "https://pub.dartlang.org" 498 | source: hosted 499 | version: "0.14.0-dev+12" 500 | nested: 501 | dependency: transitive 502 | description: 503 | name: nested 504 | url: "https://pub.dartlang.org" 505 | source: hosted 506 | version: "0.0.4" 507 | node_interop: 508 | dependency: transitive 509 | description: 510 | name: node_interop 511 | url: "https://pub.dartlang.org" 512 | source: hosted 513 | version: "1.0.3" 514 | node_io: 515 | dependency: transitive 516 | description: 517 | name: node_io 518 | url: "https://pub.dartlang.org" 519 | source: hosted 520 | version: "1.0.1+2" 521 | package_config: 522 | dependency: transitive 523 | description: 524 | name: package_config 525 | url: "https://pub.dartlang.org" 526 | source: hosted 527 | version: "1.9.3" 528 | package_resolver: 529 | dependency: transitive 530 | description: 531 | name: package_resolver 532 | url: "https://pub.dartlang.org" 533 | source: hosted 534 | version: "1.0.10" 535 | path: 536 | dependency: transitive 537 | description: 538 | name: path 539 | url: "https://pub.dartlang.org" 540 | source: hosted 541 | version: "1.6.4" 542 | path_drawing: 543 | dependency: transitive 544 | description: 545 | name: path_drawing 546 | url: "https://pub.dartlang.org" 547 | source: hosted 548 | version: "0.4.1" 549 | path_parsing: 550 | dependency: transitive 551 | description: 552 | name: path_parsing 553 | url: "https://pub.dartlang.org" 554 | source: hosted 555 | version: "0.1.4" 556 | path_provider: 557 | dependency: transitive 558 | description: 559 | name: path_provider 560 | url: "https://pub.dartlang.org" 561 | source: hosted 562 | version: "1.6.5" 563 | path_provider_macos: 564 | dependency: transitive 565 | description: 566 | name: path_provider_macos 567 | url: "https://pub.dartlang.org" 568 | source: hosted 569 | version: "0.0.4" 570 | path_provider_platform_interface: 571 | dependency: transitive 572 | description: 573 | name: path_provider_platform_interface 574 | url: "https://pub.dartlang.org" 575 | source: hosted 576 | version: "1.0.1" 577 | pedantic: 578 | dependency: transitive 579 | description: 580 | name: pedantic 581 | url: "https://pub.dartlang.org" 582 | source: hosted 583 | version: "1.9.0" 584 | pedantic_mono: 585 | dependency: "direct dev" 586 | description: 587 | name: pedantic_mono 588 | url: "https://pub.dartlang.org" 589 | source: hosted 590 | version: "1.8.0" 591 | petitparser: 592 | dependency: transitive 593 | description: 594 | name: petitparser 595 | url: "https://pub.dartlang.org" 596 | source: hosted 597 | version: "2.4.0" 598 | pigment: 599 | dependency: transitive 600 | description: 601 | name: pigment 602 | url: "https://pub.dartlang.org" 603 | source: hosted 604 | version: "1.0.3" 605 | platform: 606 | dependency: transitive 607 | description: 608 | name: platform 609 | url: "https://pub.dartlang.org" 610 | source: hosted 611 | version: "2.2.1" 612 | plugin_platform_interface: 613 | dependency: transitive 614 | description: 615 | name: plugin_platform_interface 616 | url: "https://pub.dartlang.org" 617 | source: hosted 618 | version: "1.0.2" 619 | pool: 620 | dependency: transitive 621 | description: 622 | name: pool 623 | url: "https://pub.dartlang.org" 624 | source: hosted 625 | version: "1.4.0" 626 | provider: 627 | dependency: "direct main" 628 | description: 629 | name: provider 630 | url: "https://pub.dartlang.org" 631 | source: hosted 632 | version: "4.0.4" 633 | pub_semver: 634 | dependency: transitive 635 | description: 636 | name: pub_semver 637 | url: "https://pub.dartlang.org" 638 | source: hosted 639 | version: "1.4.4" 640 | pubspec_parse: 641 | dependency: transitive 642 | description: 643 | name: pubspec_parse 644 | url: "https://pub.dartlang.org" 645 | source: hosted 646 | version: "0.1.5" 647 | quiver: 648 | dependency: transitive 649 | description: 650 | name: quiver 651 | url: "https://pub.dartlang.org" 652 | source: hosted 653 | version: "2.0.5" 654 | route_observer_mixin: 655 | dependency: "direct main" 656 | description: 657 | name: route_observer_mixin 658 | url: "https://pub.dartlang.org" 659 | source: hosted 660 | version: "1.1.0" 661 | rxdart: 662 | dependency: "direct main" 663 | description: 664 | name: rxdart 665 | url: "https://pub.dartlang.org" 666 | source: hosted 667 | version: "0.23.1" 668 | shelf: 669 | dependency: transitive 670 | description: 671 | name: shelf 672 | url: "https://pub.dartlang.org" 673 | source: hosted 674 | version: "0.7.5" 675 | shelf_web_socket: 676 | dependency: transitive 677 | description: 678 | name: shelf_web_socket 679 | url: "https://pub.dartlang.org" 680 | source: hosted 681 | version: "0.2.3" 682 | simple_logger: 683 | dependency: "direct main" 684 | description: 685 | name: simple_logger 686 | url: "https://pub.dartlang.org" 687 | source: hosted 688 | version: "1.7.0" 689 | sky_engine: 690 | dependency: transitive 691 | description: flutter 692 | source: sdk 693 | version: "0.0.99" 694 | source_gen: 695 | dependency: transitive 696 | description: 697 | name: source_gen 698 | url: "https://pub.dartlang.org" 699 | source: hosted 700 | version: "0.9.5" 701 | source_span: 702 | dependency: transitive 703 | description: 704 | name: source_span 705 | url: "https://pub.dartlang.org" 706 | source: hosted 707 | version: "1.5.5" 708 | sqflite: 709 | dependency: transitive 710 | description: 711 | name: sqflite 712 | url: "https://pub.dartlang.org" 713 | source: hosted 714 | version: "1.3.0" 715 | sqflite_common: 716 | dependency: transitive 717 | description: 718 | name: sqflite_common 719 | url: "https://pub.dartlang.org" 720 | source: hosted 721 | version: "1.0.0+1" 722 | stack_trace: 723 | dependency: transitive 724 | description: 725 | name: stack_trace 726 | url: "https://pub.dartlang.org" 727 | source: hosted 728 | version: "1.9.3" 729 | stream_channel: 730 | dependency: transitive 731 | description: 732 | name: stream_channel 733 | url: "https://pub.dartlang.org" 734 | source: hosted 735 | version: "2.0.0" 736 | stream_transform: 737 | dependency: transitive 738 | description: 739 | name: stream_transform 740 | url: "https://pub.dartlang.org" 741 | source: hosted 742 | version: "1.2.0" 743 | string_scanner: 744 | dependency: transitive 745 | description: 746 | name: string_scanner 747 | url: "https://pub.dartlang.org" 748 | source: hosted 749 | version: "1.0.5" 750 | subscription_holder: 751 | dependency: transitive 752 | description: 753 | name: subscription_holder 754 | url: "https://pub.dartlang.org" 755 | source: hosted 756 | version: "1.2.0+1" 757 | synchronized: 758 | dependency: transitive 759 | description: 760 | name: synchronized 761 | url: "https://pub.dartlang.org" 762 | source: hosted 763 | version: "2.2.0" 764 | term_glyph: 765 | dependency: transitive 766 | description: 767 | name: term_glyph 768 | url: "https://pub.dartlang.org" 769 | source: hosted 770 | version: "1.1.0" 771 | test_api: 772 | dependency: transitive 773 | description: 774 | name: test_api 775 | url: "https://pub.dartlang.org" 776 | source: hosted 777 | version: "0.2.15" 778 | timing: 779 | dependency: transitive 780 | description: 781 | name: timing 782 | url: "https://pub.dartlang.org" 783 | source: hosted 784 | version: "0.1.1+2" 785 | tinycolor: 786 | dependency: transitive 787 | description: 788 | name: tinycolor 789 | url: "https://pub.dartlang.org" 790 | source: hosted 791 | version: "1.0.2" 792 | typed_data: 793 | dependency: transitive 794 | description: 795 | name: typed_data 796 | url: "https://pub.dartlang.org" 797 | source: hosted 798 | version: "1.1.6" 799 | undraw: 800 | dependency: "direct main" 801 | description: 802 | name: undraw 803 | url: "https://pub.dartlang.org" 804 | source: hosted 805 | version: "1.0.1" 806 | url_launcher: 807 | dependency: transitive 808 | description: 809 | name: url_launcher 810 | url: "https://pub.dartlang.org" 811 | source: hosted 812 | version: "5.4.2" 813 | url_launcher_macos: 814 | dependency: transitive 815 | description: 816 | name: url_launcher_macos 817 | url: "https://pub.dartlang.org" 818 | source: hosted 819 | version: "0.0.1+4" 820 | url_launcher_platform_interface: 821 | dependency: transitive 822 | description: 823 | name: url_launcher_platform_interface 824 | url: "https://pub.dartlang.org" 825 | source: hosted 826 | version: "1.0.6" 827 | url_launcher_web: 828 | dependency: transitive 829 | description: 830 | name: url_launcher_web 831 | url: "https://pub.dartlang.org" 832 | source: hosted 833 | version: "0.1.1+1" 834 | uuid: 835 | dependency: transitive 836 | description: 837 | name: uuid 838 | url: "https://pub.dartlang.org" 839 | source: hosted 840 | version: "2.0.4" 841 | vector_math: 842 | dependency: transitive 843 | description: 844 | name: vector_math 845 | url: "https://pub.dartlang.org" 846 | source: hosted 847 | version: "2.0.8" 848 | watcher: 849 | dependency: transitive 850 | description: 851 | name: watcher 852 | url: "https://pub.dartlang.org" 853 | source: hosted 854 | version: "0.9.7+14" 855 | web_socket_channel: 856 | dependency: transitive 857 | description: 858 | name: web_socket_channel 859 | url: "https://pub.dartlang.org" 860 | source: hosted 861 | version: "1.1.0" 862 | xml: 863 | dependency: transitive 864 | description: 865 | name: xml 866 | url: "https://pub.dartlang.org" 867 | source: hosted 868 | version: "3.5.0" 869 | yaml: 870 | dependency: transitive 871 | description: 872 | name: yaml 873 | url: "https://pub.dartlang.org" 874 | source: hosted 875 | version: "2.2.0" 876 | sdks: 877 | dart: ">=2.7.0 <3.0.0" 878 | flutter: ">=1.14.6 <2.0.0" 879 | -------------------------------------------------------------------------------- /flutter/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: google_tasks 2 | description: Google Tasks clone 3 | version: 1.0.0+1 4 | environment: 5 | sdk: ">=2.7.0 <3.0.0" 6 | dependencies: 7 | flutter: 8 | sdk: flutter 9 | flutter_localizations: 10 | sdk: flutter 11 | cloud_firestore: any 12 | cupertino_icons: any 13 | disposable_provider: any 14 | firebase_analytics: any 15 | firebase_auth: any 16 | firebase_core: any 17 | firebase_crashlytics: any 18 | firestore_ref: ^0.8.0-dev 19 | freezed_annotation: any 20 | json_annotation: any 21 | mono_kit: ^0.14.0-dev 22 | firebase_performance: any 23 | provider: any 24 | route_observer_mixin: any 25 | rxdart: any 26 | simple_logger: any 27 | undraw: any 28 | google_sign_in: any 29 | cloud_functions: any 30 | cached_network_image: any 31 | flutter_svg: ^0.17.3+1 32 | dev_dependencies: 33 | flutter_test: 34 | sdk: flutter 35 | intl_translation: any 36 | build_runner: any 37 | freezed: any 38 | json_serializable: any 39 | pedantic_mono: any 40 | flutter: 41 | uses-material-design: true -------------------------------------------------------------------------------- /flutter/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 | void main() {} 9 | -------------------------------------------------------------------------------- /flutter/update_l10n.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # arbファイル(文言リソースのJSONファイル)の作成 4 | flutter packages pub run intl_translation:extract_to_arb \ 5 | --locale=messages \ 6 | --output-dir=lib/l10n \ 7 | lib/l10n/messages.dart 8 | 9 | # 生成された雛形のintl_messages.arbをコピーしてintl_en.arbを作成 10 | # 警告抑制のため、@@localeだけ指定 11 | # 言語リソースが見つからなかったらIntl.messageに指定されている文言が使われるので 12 | # デフォルト文言のarbは不要かも 13 | cat lib/l10n/intl_messages.arb | \ 14 | sed -e 's/"@@locale": "messages"/"@@locale": "en"/g' > \ 15 | lib/l10n/intl_en.arb 16 | 17 | # このタイミングで、必要に応じて、メインの言語以外のarbファイルを用意 18 | 19 | # arbファイル群から多言語対応に必要なクラスを生成 20 | flutter packages pub run intl_translation:generate_from_arb \ 21 | --output-dir=lib/l10n \ 22 | --no-use-deferred-loading \ 23 | lib/l10n/messages.dart \ 24 | lib/l10n/intl_*.arb 25 | --------------------------------------------------------------------------------