├── .gradle └── 4.4 │ ├── fileChanges │ └── last-build.bin │ └── fileHashes │ └── fileHashes.lock ├── Login.png ├── .firebaserc ├── assets ├── today.png └── calendar.png ├── events_calendar.png ├── events_calendar_diagram.png ├── .gitignore ├── android ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── app │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── 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 │ │ │ └── drawable │ │ │ │ └── launch_background.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── graham │ │ │ │ └── calendar │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ ├── google-services.json │ └── build.gradle ├── .gitignore ├── settings.gradle ├── build.gradle ├── gradlew.bat └── gradlew ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── AppDelegate.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── 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-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── main.m │ ├── AppDelegate.m │ ├── GoogleService-Info.plist │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── project.pbxproj ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── .gitignore └── Podfile ├── firebase.json ├── lib ├── models │ └── event_model.dart ├── global_contants.dart ├── authentication.dart ├── contact_creator.dart ├── splash_screen.dart ├── gift_creator.dart ├── contact_details.dart ├── contacts_view.dart ├── event_creator.dart ├── event_view.dart ├── sign_in_vew.dart └── main.dart ├── .metadata ├── local.properties ├── functions ├── package.json ├── .eslintrc.json └── index.js ├── README.md ├── test └── widget_test.dart ├── flutter_widget_app_android.iml ├── pubspec.yaml ├── flutter_calendar.iml └── pubspec.lock /.gradle/4.4/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/Login.png -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "calendar-f34b8" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /assets/today.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/assets/today.png -------------------------------------------------------------------------------- /assets/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/assets/calendar.png -------------------------------------------------------------------------------- /events_calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/events_calendar.png -------------------------------------------------------------------------------- /events_calendar_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/events_calendar_diagram.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | 5 | -------------------------------------------------------------------------------- /.gradle/4.4/fileHashes/fileHashes.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/.gradle/4.4/fileHashes/fileHashes.lock -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "predeploy": [ 4 | "npm --prefix \"$RESOURCE_DIR\" run lint" 5 | ], 6 | "source": "functions" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattgraham1/FlutterCalendar/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lib/models/event_model.dart: -------------------------------------------------------------------------------- 1 | 2 | class Event { 3 | final String title; 4 | final String summary; 5 | final DateTime time; 6 | final String documentId; 7 | 8 | Event(this.title, this.summary, this.time, [this.documentId]); 9 | } -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 11 22:54:01 CDT 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-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.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: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b 8 | channel: beta 9 | -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Thu Aug 09 08:52:09 CDT 2018 8 | sdk.dir=/Users/mgraham/Library/Android/sdk 9 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/graham/calendar/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.graham.calendar; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/global_contants.dart: -------------------------------------------------------------------------------- 1 | 2 | class Constants { 3 | // Firebase collection IDs 4 | static String usersCollectionId = 'users'; 5 | static String calendarContactsCollectionId = 'contacts'; 6 | static String contactGiftsCollectionId = 'contact_gifts'; 7 | 8 | // Flutter Routes 9 | static String splashRoute = '/splash'; 10 | static String calendarRoute = '/calendar'; 11 | static String eventCreatorRoute = '/event_creator'; 12 | static String calContactsRoute = '/calendar_contacts'; 13 | static String signInRoute = '/sign_in_page'; 14 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "lint": "eslint .", 6 | "serve": "firebase serve --only functions", 7 | "shell": "firebase functions:shell", 8 | "start": "npm run shell", 9 | "deploy": "firebase deploy --only functions", 10 | "logs": "firebase functions:log" 11 | }, 12 | "dependencies": { 13 | "collections": "^5.1.5", 14 | "firebase-admin": "~6.0.0", 15 | "firebase-functions": "^2.0.3", 16 | "firebase-tools": "^6.3.1" 17 | }, 18 | "devDependencies": { 19 | "eslint": "^4.12.0", 20 | "eslint-plugin-promise": "^3.6.0" 21 | }, 22 | "engines": { 23 | "node": "8" 24 | }, 25 | "private": true 26 | } 27 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | mavenLocal() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.3.0' 10 | classpath 'com.google.gms:google-services:4.2.0' 11 | } 12 | } 13 | 14 | allprojects { 15 | configurations.all { 16 | } 17 | 18 | repositories { 19 | google() 20 | jcenter() 21 | mavenLocal() 22 | } 23 | } 24 | 25 | rootProject.buildDir = '../build' 26 | subprojects { 27 | project.buildDir = "${rootProject.buildDir}/${project.name}" 28 | } 29 | subprojects { 30 | project.evaluationDependsOn(':app') 31 | } 32 | 33 | task clean(type: Delete) { 34 | delete rootProject.buildDir 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Application 2 | A new Flutter application implementing a simple mobile calendar app for storing basic events into Firebase cloud database. 3 | 4 | ## Google Play Store Link: 5 | https://play.google.com/store/apps/details?id=com.graham.calendar 6 | 7 | ## Tools: 8 | cron-job link: https://cron-job.org 9 | 10 | ## Diagram of basic architecture 11 | ![Screenshot](events_calendar_diagram.png) 12 | 13 | ## Firebase Cloud Firestore Database Tree 14 | ``` 15 | calendar_events (collection) 16 | | 17 | - email (field as string) 18 | - name (field as string) 19 | - summary (field as string) 20 | - time (field as timestamp) 21 | 22 | users (collection) 23 | | 24 | |- contacts (collection) 25 | | | 26 | | |- contact_gifts (collection) 27 | | | | 28 | | | |- cost (field as number) 29 | | | |- name (field as string) 30 | | |- name(field as string) 31 | | 32 | |- email (field as string) 33 | |- token (field as string) 34 | ``` 35 | 36 | ## Screen Shots: 37 | 38 | ![Screenshot1](Login.png) 39 | 40 | ![Screenshot2](events_calendar.png) 41 | 42 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:flutter_widget_app/main.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "418260216398", 4 | "firebase_url": "https://calendar-f34b8.firebaseio.com", 5 | "project_id": "calendar-f34b8", 6 | "storage_bucket": "calendar-f34b8.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:418260216398:android:cae245f6a1ecb55b", 12 | "android_client_info": { 13 | "package_name": "com.graham.calendar" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "418260216398-7m0tlmqkhgl47dke4kqqk2jg0ef1h263.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyBtUN9M-EDbaqv1gFjJDDc5CdOxdwKglMw" 25 | } 26 | ], 27 | "services": { 28 | "analytics_service": { 29 | "status": 1 30 | }, 31 | "appinvite_service": { 32 | "status": 1, 33 | "other_platform_oauth_client": [] 34 | }, 35 | "ads_service": { 36 | "status": 2 37 | } 38 | } 39 | } 40 | ], 41 | "configuration_version": "1" 42 | } -------------------------------------------------------------------------------- /lib/authentication.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:google_sign_in/google_sign_in.dart'; 4 | 5 | class AuthHelper { 6 | static final AuthHelper _singleton = new AuthHelper._internal(); 7 | 8 | factory AuthHelper() { 9 | return _singleton; 10 | } 11 | 12 | FirebaseAuth _auth; 13 | GoogleSignIn _googleSignIn; 14 | 15 | AuthHelper._internal() { 16 | _auth = FirebaseAuth.instance; 17 | _googleSignIn = new GoogleSignIn(); 18 | } 19 | 20 | Future signInWithGoogle() async { 21 | final GoogleSignInAccount googleUser = await _googleSignIn.signIn(); 22 | final GoogleSignInAuthentication googleAuth = await googleUser?.authentication; 23 | 24 | final AuthCredential credential = GoogleAuthProvider.getCredential( 25 | accessToken: googleAuth.accessToken, 26 | idToken: googleAuth.idToken, 27 | ); 28 | 29 | final FirebaseUser user = await _auth.signInWithCredential(credential); 30 | final FirebaseUser currentUser = await _auth?.currentUser(); 31 | assert(user?.uid == currentUser?.uid); 32 | 33 | print('signInWithGoogle succeeded: $user'); 34 | return user; 35 | } 36 | 37 | Future signOutWithGoogle() async { 38 | await _auth.signOut(); 39 | await _googleSignIn.signOut(); 40 | } 41 | 42 | Future signOut() async { 43 | await _auth.signOut(); 44 | } 45 | } -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AD_UNIT_ID_FOR_BANNER_TEST 6 | ca-app-pub-3940256099942544/2934735716 7 | AD_UNIT_ID_FOR_INTERSTITIAL_TEST 8 | ca-app-pub-3940256099942544/4411468910 9 | CLIENT_ID 10 | 418260216398-c3eq6ico2462hd01tjiuu902tvak8ef3.apps.googleusercontent.com 11 | REVERSED_CLIENT_ID 12 | com.googleusercontent.apps.418260216398-c3eq6ico2462hd01tjiuu902tvak8ef3 13 | API_KEY 14 | AIzaSyBkL5SrLlCPMw3WffcpUeTVOHqA1bpaplQ 15 | GCM_SENDER_ID 16 | 418260216398 17 | PLIST_VERSION 18 | 1 19 | BUNDLE_ID 20 | com.graham.calendar 21 | PROJECT_ID 22 | calendar-f34b8 23 | STORAGE_BUCKET 24 | calendar-f34b8.appspot.com 25 | IS_ADS_ENABLED 26 | 27 | IS_ANALYTICS_ENABLED 28 | 29 | IS_APPINVITE_ENABLED 30 | 31 | IS_GCM_ENABLED 32 | 33 | IS_SIGNIN_ENABLED 34 | 35 | GOOGLE_APP_ID 36 | 1:418260216398:ios:cae245f6a1ecb55b 37 | DATABASE_URL 38 | https://calendar-f34b8.firebaseio.com 39 | 40 | -------------------------------------------------------------------------------- /flutter_widget_app_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Calendar 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_widget_app 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /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 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | def keystoreProperties = new Properties() 18 | def keystorePropertiesFile = rootProject.file('key.properties') 19 | if (keystorePropertiesFile.exists()) { 20 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 21 | } 22 | 23 | android { 24 | compileSdkVersion 28 25 | 26 | lintOptions { 27 | disable 'InvalidPackage' 28 | } 29 | 30 | defaultConfig { 31 | applicationId "com.graham.calendar" 32 | minSdkVersion 23 33 | targetSdkVersion 28 34 | versionCode 1 35 | versionName "1.2" 36 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 37 | } 38 | 39 | signingConfigs { 40 | release { 41 | keyAlias keystoreProperties['keyAlias'] 42 | keyPassword keystoreProperties['keyPassword'] 43 | storeFile file(keystoreProperties['storeFile']) 44 | storePassword keystoreProperties['storePassword'] 45 | } 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig signingConfigs.debug 52 | //signingConfig signingConfigs.release 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | testImplementation 'junit:junit:4.12' 63 | androidTestImplementation 'androidx.test:runner:1.1.1' 64 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 65 | } 66 | 67 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '10.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | def parse_KV_file(file, separator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=separator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname, :path => podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 31 | # referring to absolute paths on developers' machines. 32 | system('rm -rf .symlinks') 33 | system('mkdir -p .symlinks/plugins') 34 | 35 | # Flutter Pods 36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 37 | if generated_xcode_build_settings.empty? 38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 39 | end 40 | generated_xcode_build_settings.map { |p| 41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 42 | symlink = File.join('.symlinks', 'flutter') 43 | File.symlink(File.dirname(p[:path]), symlink) 44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 45 | end 46 | } 47 | 48 | # Plugin Pods 49 | plugin_pods = parse_KV_file('../.flutter-plugins') 50 | plugin_pods.map { |p| 51 | symlink = File.join('.symlinks', 'plugins', p[:name]) 52 | File.symlink(p[:path], symlink) 53 | pod p[:name], :path => File.join(symlink, 'ios') 54 | } 55 | end 56 | 57 | post_install do |installer| 58 | installer.pods_project.targets.each do |target| 59 | target.build_configurations.each do |config| 60 | config.build_settings['SWIFT_VERSION'] = '4.1' # required by simple_permission 61 | config.build_settings['ENABLE_BITCODE'] = 'NO' 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_widget_app 2 | description: A new Flutter application. 3 | 4 | dependencies: 5 | flutter: 6 | sdk: flutter 7 | 8 | # The following adds the Cupertino Icons font to your application. 9 | # Use with the CupertinoIcons class for iOS style icons. 10 | cupertino_icons: ^0.1.2 11 | 12 | # Google dependencies 13 | firebase_core: 0.3.0 14 | cloud_firestore: 0.9.5+2 15 | google_sign_in: 4.0.1+1 16 | firebase_auth: 0.8.0+3 17 | firebase_messaging: ^4.0.0 18 | cloud_functions: 0.1.2 19 | 20 | #Google / Social media buttons 21 | flutter_auth_buttons: ^0.3.1 22 | 23 | #Date time picker plugin 24 | datetime_picker_formfield: ^0.1.8 25 | 26 | dev_dependencies: 27 | flutter_test: 28 | sdk: flutter 29 | 30 | 31 | # For information on the generic Dart part of this file, see the 32 | # following page: https://www.dartlang.org/tools/pub/pubspec 33 | 34 | # The following section is specific to Flutter. 35 | flutter: 36 | 37 | # The following line ensures that the Material Icons font is 38 | # included with your application, so that you can use the icons in 39 | # the material Icons class. 40 | uses-material-design: true 41 | 42 | # To add assets to your application, add an assets section, like this: 43 | # assets: 44 | # - images/a_dot_burr.jpeg 45 | # - images/a_dot_ham.jpeg 46 | assets: 47 | - assets/calendar.png 48 | 49 | # An image asset can refer to one or more resolution-specific "variants", see 50 | # https://flutter.io/assets-and-images/#resolution-aware. 51 | 52 | # For details regarding adding assets from package dependencies, see 53 | # https://flutter.io/assets-and-images/#from-packages 54 | 55 | # To add custom fonts to your application, add a fonts section here, 56 | # in this "flutter" section. Each entry in this list should have a 57 | # "family" key with the font family name, and a "fonts" key with a 58 | # list giving the asset and other descriptors for the font. For 59 | # example: 60 | # fonts: 61 | # - family: Schyler 62 | # fonts: 63 | # - asset: fonts/Schyler-Regular.ttf 64 | # - asset: fonts/Schyler-Italic.ttf 65 | # style: italic 66 | # - family: Trajan Pro 67 | # fonts: 68 | # - asset: fonts/TrajanPro.ttf 69 | # - asset: fonts/TrajanPro_Bold.ttf 70 | # weight: 700 71 | # 72 | # For details regarding fonts from package dependencies, 73 | # see https://flutter.io/custom-fonts/#from-packages 74 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 16 | 20 | 23 | 30 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /lib/contact_creator.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_auth/firebase_auth.dart'; 2 | import 'package:cloud_firestore/cloud_firestore.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_widget_app/global_contants.dart'; 5 | 6 | class ContactCreator extends StatefulWidget { 7 | final String userDocumentId; 8 | 9 | ContactCreator({this.userDocumentId}); 10 | 11 | @override 12 | State createState() { 13 | return new _ContactCreateState(); 14 | } 15 | } 16 | 17 | class _ContactCreateState extends State { 18 | final GlobalKey _formKey = new GlobalKey(); 19 | final FirebaseAuth _auth = FirebaseAuth.instance; 20 | String _contactName; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | 25 | final contactNameWidget = new TextFormField( 26 | keyboardType: TextInputType.text, 27 | decoration: new InputDecoration( 28 | hintText: 'Fred Flinstone', 29 | labelText: 'Contact Name', 30 | contentPadding: EdgeInsets.all(16.0), 31 | border: OutlineInputBorder( 32 | borderRadius: BorderRadius.circular(8.0), 33 | ) 34 | ), 35 | style: Theme.of(context).textTheme.headline, 36 | validator: (value) { 37 | if(value.isEmpty) { 38 | return 'Please enter a name.'; 39 | } 40 | }, 41 | onSaved: (String value) => this._contactName = value, 42 | ); 43 | 44 | return Scaffold( 45 | appBar: new AppBar( 46 | leading: new BackButton(), 47 | title: new Text('Create New Event'), 48 | actions: [ 49 | new Container( 50 | alignment: Alignment.center, 51 | padding: EdgeInsets.all(15.0), 52 | child: new InkWell( 53 | child: new Text( 54 | 'SAVE', 55 | style: TextStyle( 56 | fontSize: 20.0), 57 | ), 58 | onTap: () => _saveNewContact(context), 59 | ), 60 | ) 61 | ], 62 | ), 63 | body: new Form( 64 | key: this._formKey, 65 | child: new Container( 66 | padding: EdgeInsets.all(10.0), 67 | child: new Column( 68 | children: [ 69 | contactNameWidget, 70 | ], 71 | ), 72 | )), 73 | ); 74 | } 75 | 76 | _saveNewContact(BuildContext context) async { 77 | FirebaseUser currentUser = await _auth.currentUser(); 78 | 79 | if (currentUser != null && this._formKey.currentState.validate()) { 80 | _formKey.currentState.save(); // Save our form now. 81 | 82 | Firestore.instance.collection(Constants.usersCollectionId) 83 | .document(widget.userDocumentId) 84 | .collection(Constants.calendarContactsCollectionId) 85 | .document(null) 86 | .setData({'name': _contactName}); 87 | 88 | Navigator.maybePop(context); 89 | } else { 90 | print('Error validating data and saving to firestore.'); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /flutter_calendar.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /lib/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter_widget_app/authentication.dart'; 4 | import 'package:flutter_auth_buttons/flutter_auth_buttons.dart'; 5 | import 'package:flutter_widget_app/global_contants.dart'; 6 | 7 | class SplashPage extends StatefulWidget { 8 | @override 9 | State createState() => new _SplashPageState(); 10 | } 11 | 12 | class _SplashPageState extends State { 13 | 14 | void _navigateToCalendarView() { 15 | Navigator.of(context).pushNamedAndRemoveUntil(Constants.calendarRoute, 16 | (Route route) => false); 17 | } 18 | 19 | void _navigateToSignInPage() { 20 | Navigator.of(context).pushNamed(Constants.signInRoute); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final signInWithGoogleButton = GoogleSignInButton( 26 | onPressed: () async { 27 | AuthHelper authHelper = new AuthHelper(); 28 | 29 | FirebaseUser user = await authHelper.signInWithGoogle().catchError((onError) { 30 | // Handle errors if needed 31 | }); 32 | 33 | if(user != null) { 34 | _navigateToCalendarView(); 35 | } else { 36 | print("Error signing in with Google."); 37 | } 38 | }, 39 | darkMode: true); 40 | 41 | final signInWithEmailButton = ButtonTheme( 42 | height: 40.0, 43 | padding: const EdgeInsets.all(0.0), 44 | shape: RoundedRectangleBorder( 45 | // Google doesn't specify a border radius, but this looks about right. 46 | borderRadius: BorderRadius.circular(3.0), 47 | ), 48 | child: RaisedButton( 49 | onPressed: _navigateToSignInPage, 50 | color: Color(0xFF4285F4), 51 | child: Row( 52 | mainAxisSize: MainAxisSize.min, 53 | children: [ 54 | Padding(padding: const EdgeInsets.all(1.0), 55 | child: Container( 56 | height: 38.0, // 40dp - 2*1dp border 57 | width: 38.0, // matches above 58 | decoration: BoxDecoration( 59 | borderRadius: BorderRadius.circular(3.0), 60 | ), 61 | child: Center( 62 | child: Icon(Icons.email, color: Colors.white, size: 38), 63 | ), 64 | ), 65 | ), 66 | SizedBox(width: 14.0 /* 24.0 - 10dp padding */), 67 | Padding( 68 | padding: const EdgeInsets.fromLTRB(0.0, 8.0, 8.0, 8.0), 69 | child: Text('Sign in with email', 70 | style: TextStyle(fontSize: 18.0, fontFamily: "Roboto", fontWeight: FontWeight.w500, color: Colors.white), 71 | ), 72 | ), 73 | ], 74 | ), 75 | ), 76 | ); 77 | 78 | final loginImage = new Image.asset('assets/calendar.png', 79 | height: 128.0, 80 | ); 81 | 82 | return new Scaffold( 83 | backgroundColor: Colors.white, 84 | body: Padding( 85 | padding: const EdgeInsets.only(left: 24.0, right: 24.0), 86 | child: SafeArea( 87 | child: Column( 88 | crossAxisAlignment: CrossAxisAlignment.stretch, 89 | mainAxisAlignment: MainAxisAlignment.center, 90 | children: [ 91 | Center(child: Text("Events Calendar", 92 | style: TextStyle(fontSize: 38, fontWeight: FontWeight.bold), textAlign: TextAlign.center) 93 | ), 94 | loginImage, 95 | SizedBox(height: 8.0), 96 | signInWithGoogleButton, 97 | Center(child: Text("or")), 98 | SizedBox(height: 4.0), 99 | signInWithEmailButton, 100 | ], 101 | ), 102 | ), 103 | ) 104 | ); 105 | } 106 | } -------------------------------------------------------------------------------- /lib/gift_creator.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_widget_app/global_contants.dart'; 5 | 6 | class GiftCreator extends StatefulWidget { 7 | final String userDocumentId; 8 | final String userContactDocumentId; 9 | 10 | GiftCreator({this.userDocumentId, this.userContactDocumentId}); 11 | 12 | @override 13 | State createState() { 14 | return new _GiftCreatorState(); 15 | } 16 | } 17 | 18 | class _GiftCreatorState extends State { 19 | final GlobalKey _formKey = new GlobalKey(); 20 | final FirebaseAuth _auth = FirebaseAuth.instance; 21 | String _giftName; 22 | int _giftCost; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | final giftNameWidget = new TextFormField( 27 | keyboardType: TextInputType.text, 28 | decoration: new InputDecoration( 29 | hintText: 'Gift Card', 30 | labelText: 'Gift Name', 31 | contentPadding: EdgeInsets.all(16.0), 32 | border: OutlineInputBorder( 33 | borderRadius: BorderRadius.circular(8.0), 34 | ) 35 | ), 36 | style: Theme.of(context).textTheme.headline, 37 | validator: (value) { 38 | if(value.isEmpty) { 39 | return 'Please enter a gift name.'; 40 | } 41 | }, 42 | onSaved: (String value) => this._giftName = value, 43 | ); 44 | 45 | final giftCostWidget = new TextFormField( 46 | keyboardType: TextInputType.number, 47 | decoration: new InputDecoration( 48 | hintText: '25', 49 | labelText: 'Gift Amount', 50 | contentPadding: EdgeInsets.all(16.0), 51 | border: OutlineInputBorder( 52 | borderRadius: BorderRadius.circular(8.0), 53 | ) 54 | ), 55 | style: Theme.of(context).textTheme.headline, 56 | validator: (value) { 57 | if(value.isEmpty) { 58 | return 'Please enter a gift amount.'; 59 | } 60 | }, 61 | onSaved: (String value) => this._giftCost = int.parse(value), 62 | ); 63 | 64 | return Scaffold( 65 | appBar: new AppBar( 66 | leading: new BackButton(), 67 | title: new Text('Create New Gift'), 68 | actions: [ 69 | new Container( 70 | alignment: Alignment.center, 71 | padding: EdgeInsets.all(15.0), 72 | child: new InkWell( 73 | child: new Text( 74 | 'SAVE', 75 | style: TextStyle( 76 | fontSize: 20.0), 77 | ), 78 | onTap: () => _saveNewContact(context), 79 | ), 80 | ) 81 | ], 82 | ), 83 | body: new Form( 84 | key: this._formKey, 85 | child: new Container( 86 | padding: EdgeInsets.all(10.0), 87 | child: new Column( 88 | children: [ 89 | giftNameWidget, 90 | SizedBox(height: 8.0), 91 | giftCostWidget, 92 | ], 93 | ), 94 | )), 95 | ); 96 | } 97 | 98 | _saveNewContact(BuildContext context) async { 99 | FirebaseUser currentUser = await _auth.currentUser(); 100 | 101 | if (currentUser != null && this._formKey.currentState.validate()) { 102 | _formKey.currentState.save(); // Save our form now. 103 | 104 | Firestore.instance.collection(Constants.usersCollectionId) 105 | .document(widget.userDocumentId) 106 | .collection(Constants.calendarContactsCollectionId) 107 | .document(widget.userContactDocumentId) 108 | .collection(Constants.contactGiftsCollectionId) 109 | .document(null) 110 | .setData({'name': _giftName, 'cost': _giftCost}); 111 | 112 | Navigator.maybePop(context); 113 | } else { 114 | print('Error validating data and saving to firestore.'); 115 | } 116 | } 117 | 118 | } -------------------------------------------------------------------------------- /functions/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | // Required for certain syntax usages 4 | "ecmaVersion": 6 5 | }, 6 | "plugins": [ 7 | "promise" 8 | ], 9 | "extends": "eslint:recommended", 10 | "rules": { 11 | // Removed rule "disallow the use of console" from recommended eslint rules 12 | "no-console": "off", 13 | 14 | // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules 15 | "no-regex-spaces": "off", 16 | 17 | // Removed rule "disallow the use of debugger" from recommended eslint rules 18 | "no-debugger": "off", 19 | 20 | // Removed rule "disallow unused variables" from recommended eslint rules 21 | "no-unused-vars": "off", 22 | 23 | // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules 24 | "no-mixed-spaces-and-tabs": "off", 25 | 26 | // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules 27 | "no-undef": "off", 28 | 29 | // Warn against template literal placeholder syntax in regular strings 30 | "no-template-curly-in-string": 1, 31 | 32 | // Warn if return statements do not either always or never specify values 33 | "consistent-return": 1, 34 | 35 | // Warn if no return statements in callbacks of array methods 36 | "array-callback-return": 1, 37 | 38 | // Require the use of === and !== 39 | "eqeqeq": 2, 40 | 41 | // Disallow the use of alert, confirm, and prompt 42 | "no-alert": 2, 43 | 44 | // Disallow the use of arguments.caller or arguments.callee 45 | "no-caller": 2, 46 | 47 | // Disallow null comparisons without type-checking operators 48 | "no-eq-null": 2, 49 | 50 | // Disallow the use of eval() 51 | "no-eval": 2, 52 | 53 | // Warn against extending native types 54 | "no-extend-native": 1, 55 | 56 | // Warn against unnecessary calls to .bind() 57 | "no-extra-bind": 1, 58 | 59 | // Warn against unnecessary labels 60 | "no-extra-label": 1, 61 | 62 | // Disallow leading or trailing decimal points in numeric literals 63 | "no-floating-decimal": 2, 64 | 65 | // Warn against shorthand type conversions 66 | "no-implicit-coercion": 1, 67 | 68 | // Warn against function declarations and expressions inside loop statements 69 | "no-loop-func": 1, 70 | 71 | // Disallow new operators with the Function object 72 | "no-new-func": 2, 73 | 74 | // Warn against new operators with the String, Number, and Boolean objects 75 | "no-new-wrappers": 1, 76 | 77 | // Disallow throwing literals as exceptions 78 | "no-throw-literal": 2, 79 | 80 | // Require using Error objects as Promise rejection reasons 81 | "prefer-promise-reject-errors": 2, 82 | 83 | // Enforce “for” loop update clause moving the counter in the right direction 84 | "for-direction": 2, 85 | 86 | // Enforce return statements in getters 87 | "getter-return": 2, 88 | 89 | // Disallow await inside of loops 90 | "no-await-in-loop": 2, 91 | 92 | // Disallow comparing against -0 93 | "no-compare-neg-zero": 2, 94 | 95 | // Warn against catch clause parameters from shadowing variables in the outer scope 96 | "no-catch-shadow": 1, 97 | 98 | // Disallow identifiers from shadowing restricted names 99 | "no-shadow-restricted-names": 2, 100 | 101 | // Enforce return statements in callbacks of array methods 102 | "callback-return": 2, 103 | 104 | // Require error handling in callbacks 105 | "handle-callback-err": 2, 106 | 107 | // Warn against string concatenation with __dirname and __filename 108 | "no-path-concat": 1, 109 | 110 | // Prefer using arrow functions for callbacks 111 | "prefer-arrow-callback": 1, 112 | 113 | // Return inside each then() to create readable and reusable Promise chains. 114 | // Forces developers to return console logs and http calls in promises. 115 | "promise/always-return": 2, 116 | 117 | //Enforces the use of catch() on un-returned promises 118 | "promise/catch-or-return": 2, 119 | 120 | // Warn against nested then() or catch() statements 121 | "promise/no-nesting": 1 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/contact_details.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_widget_app/gift_creator.dart'; 4 | import 'package:flutter_widget_app/global_contants.dart'; 5 | 6 | class ContactDetails extends StatefulWidget { 7 | final String name; 8 | final String userDocumentId; 9 | final String contactDocumentId; 10 | 11 | ContactDetails({this.name, this.userDocumentId, this.contactDocumentId}); 12 | 13 | @override 14 | State createState() { 15 | return new _ContactDetailsState(name, userDocumentId, contactDocumentId); 16 | } 17 | } 18 | 19 | class _ContactDetailsState extends State { 20 | final String _name; 21 | final String _userDocumentId; 22 | final String _contactDocumentId; 23 | 24 | _ContactDetailsState(this._name, this._userDocumentId, this._contactDocumentId); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | backgroundColor: Colors.white, 30 | appBar: new AppBar( 31 | leading: new BackButton(), 32 | title: Hero( 33 | tag: _name, 34 | child: new Text(_name + ' ' + 'History', style: Theme.of(context).textTheme.title.copyWith(color: Colors.white)), 35 | ), 36 | ), 37 | floatingActionButton: new FloatingActionButton( 38 | onPressed: _onFabClicked, 39 | child: new Icon(Icons.add), 40 | ), 41 | body: FutureBuilder( 42 | future: _getContactGifts(), 43 | builder: (BuildContext context, AsyncSnapshot snapshot) { 44 | if (snapshot.hasError) 45 | return new Text('${snapshot.error}'); 46 | if (snapshot?.data?.documents?.length == 0) { 47 | return new Center( 48 | child: Text('No data available...', style: Theme.of(context).textTheme.title), 49 | ); 50 | } 51 | switch (snapshot.connectionState) { 52 | case ConnectionState.waiting: 53 | return new Center(child: new CircularProgressIndicator()); 54 | default: 55 | return ListView.separated( 56 | separatorBuilder: (context, index) => Divider( 57 | color: Colors.black, 58 | ), 59 | itemCount: snapshot?.data?.documents?.length ?? 0, 60 | itemBuilder: (BuildContext context, int index) { 61 | final giftDocument = snapshot?.data?.documents?.elementAt(index); 62 | final String _name = giftDocument.data['name']; 63 | final int _cost = giftDocument.data['cost']; 64 | return ListTile( 65 | leading: CircleAvatar( 66 | backgroundColor: Colors.orangeAccent, 67 | child: new IconButton( 68 | iconSize: 25.0, 69 | padding: EdgeInsets.all(5.0), 70 | icon: new Icon(Icons.card_giftcard, color: Colors.black), 71 | onPressed: null, 72 | )), 73 | title: Row(children: [ 74 | Expanded(child: new Column( 75 | crossAxisAlignment: CrossAxisAlignment.start, 76 | children: [ 77 | Text('Gift: ' + _name, style: Theme.of(context).textTheme.title), 78 | Text('Cost: ' + _cost.toString(), style: Theme.of(context).textTheme.title,), 79 | ], 80 | )), 81 | ],), 82 | trailing: new IconButton( 83 | iconSize: 30.0, 84 | padding: EdgeInsets.all(5.0), 85 | icon: new Icon(Icons.delete), 86 | color: Colors.black, 87 | onPressed: () => _deleteContact(giftDocument)), 88 | ); 89 | }, 90 | ); 91 | } 92 | }, 93 | ), 94 | ); 95 | } 96 | 97 | Future _getContactGifts() async { 98 | QuerySnapshot snapshot = await Firestore.instance.collection(Constants.usersCollectionId) 99 | .document(_userDocumentId) 100 | .collection(Constants.calendarContactsCollectionId) 101 | .document(_contactDocumentId) 102 | .collection(Constants.contactGiftsCollectionId) 103 | .getDocuments(); 104 | 105 | return snapshot; 106 | } 107 | 108 | Future _deleteContact(DocumentSnapshot giftDocument) async { 109 | setState(() { 110 | Firestore.instance.collection(Constants.usersCollectionId) 111 | .document(_userDocumentId) 112 | .collection(Constants.calendarContactsCollectionId) 113 | .document(_contactDocumentId) 114 | .collection(Constants.contactGiftsCollectionId) 115 | .document(giftDocument.documentID) 116 | .delete(); 117 | }); 118 | } 119 | 120 | void _onFabClicked() { 121 | Navigator.push(context, MaterialPageRoute(builder: (_) { 122 | return GiftCreator(userDocumentId: _userDocumentId, userContactDocumentId: _contactDocumentId); 123 | })); 124 | } 125 | } -------------------------------------------------------------------------------- /functions/index.js: -------------------------------------------------------------------------------- 1 | // Intiialize the Cloud Functions library 2 | const functions = require('firebase-functions'); 3 | const List = require('collections/list'); 4 | // Initialize the Firebase application with admin credentials 5 | const admin = require('firebase-admin'); 6 | const firebase_tools = require('firebase-tools'); 7 | 8 | admin.initializeApp(); 9 | 10 | // Handle firestore new date format 11 | const firestore = new admin.firestore(); 12 | const settings = {timestampsInSnapshots: true}; 13 | firestore.settings(settings); 14 | 15 | // Check for events within a days range and send notications to the associated tokens. 16 | exports.flutterCalendar = functions.https.onRequest((request, response) => { 17 | var data = []; 18 | var emailList = new List(); 19 | var now = new Date(); 20 | data.push('todays date: ' + now); 21 | data.push(now.getFullYear()); 22 | data.push(now.getMonth()); 23 | data.push(now.getDate()); 24 | 25 | firestore.collection('calendar_events') 26 | .get() 27 | .then(docs => { 28 | emailList.clear(); 29 | docs.forEach(doc => { 30 | var timestamp = doc.get('time'); 31 | var eventDate = timestamp.toDate(); 32 | data.push('event date: ' + eventDate); 33 | data.push(eventDate.getFullYear()); 34 | data.push(eventDate.getMonth()); 35 | data.push(eventDate.getDate()); 36 | // Check if we have an event within seven days 37 | if (eventDate.getFullYear() === now.getFullYear() && 38 | eventDate.getMonth() === now.getMonth() && 39 | eventDate.getDate() === now.getDate()) 40 | { 41 | // Test of adding emails to a list 42 | var mail = doc.get('email'); 43 | if(!emailList.has(mail)) { 44 | emailList.add(mail); 45 | } 46 | data.push(doc.data()); 47 | } 48 | }) 49 | 50 | data.push('email list size: ' + emailList.length); 51 | return emailList; 52 | }) 53 | // Setup the query to get all the users from the 'users' collection 54 | .then(emailList => { 55 | if(emailList.length > 0) { 56 | var query = firestore.collection('users') 57 | emailList.forEach(email => { 58 | data.push(email); 59 | query = query.where('email', '==', email.toString()); 60 | }) 61 | return query.get(); 62 | } else { 63 | return null; 64 | } 65 | }) 66 | // Get all the tokens from the 'users' collection 67 | .then(querySnapshot => { 68 | var tokenList = []; 69 | 70 | if(querySnapshot) { 71 | data.push('querySnapshot size: ' + querySnapshot.size); 72 | querySnapshot.forEach(doc => { 73 | tokenList.push(doc.get('token')); 74 | }) 75 | data.push(tokenList); 76 | } 77 | 78 | return tokenList; 79 | }) 80 | // Send the push notification to each token 81 | .then(tokens => { 82 | tokens.forEach(token => { 83 | var message = { 84 | notification: { 85 | title: 'Event Notification', 86 | body: 'You have an event coming up...' 87 | }, 88 | token: token 89 | }; 90 | data.push(message); 91 | 92 | admin.messaging().send(message); 93 | }) 94 | 95 | response.status(200).send("Successfully ran script!"); 96 | return null; 97 | }) 98 | .then(response => { 99 | //console.log('Successfully sent message:', response); 100 | return null; 101 | }) 102 | .catch(error => { 103 | console.error('Error sending push notification: ', error); 104 | response.status(501).send('Error from script...'); 105 | }); 106 | }); 107 | 108 | 109 | //============================================================= 110 | 111 | exports.deleteContactAndSubCollections = functions.https.onCall((data, context) => { 112 | console.log('deleteContactAndSubCollections function'); 113 | const contactDocumentId = data.contactDocumentId; 114 | const userDocumentId = data.userDocumentId 115 | 116 | const uid = context.auth.uid; 117 | const name = context.auth.token.name || null; 118 | const picture = context.auth.token.picture || null; 119 | const email = context.auth.token.email || null; 120 | 121 | console.log('userDocumentId: ', userDocumentId); 122 | console.log('contactDocumentId: ', contactDocumentId); 123 | console.log('uid: ', uid); 124 | console.log('name: ', name); 125 | console.log('picture: ', picture); 126 | console.log('email: ', email); 127 | 128 | // Only allow admin users to execute this function. 129 | if (!(uid && context.auth.token && email)) { 130 | throw new functions.https.HttpsError( 131 | 'permission-denied', 132 | 'Must be a valid user to initiate delete.' 133 | ); 134 | } 135 | 136 | const path = 'users' + "/" + userDocumentId + "/" + 'contacts' + "/" + contactDocumentId; 137 | console.log( 138 | `User ${context.auth.uid} has requested to delete path ${path}` 139 | ); 140 | 141 | // Run a recursive delete on the given document or collection path. 142 | // The 'token' must be set in the functions config, and can be generated 143 | // at the command line by running 'firebase login:ci'. 144 | return firebase_tools.firestore 145 | .delete(path, { 146 | project: process.env.GCLOUD_PROJECT, 147 | recursive: true, 148 | yes: true, 149 | //token: functions.config().fb.token 150 | }) 151 | .then(() => { 152 | return { 153 | path: path 154 | }; 155 | }); 156 | }); 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /lib/contacts_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:cloud_functions/cloud_functions.dart'; 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_widget_app/contact_creator.dart'; 6 | import 'package:flutter_widget_app/contact_details.dart'; 7 | import 'package:flutter_widget_app/global_contants.dart'; 8 | 9 | class CalendarContacts extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return new _CalendarContactsState(); 13 | } 14 | } 15 | 16 | class _CalendarContactsState extends State { 17 | final FirebaseAuth _auth = FirebaseAuth.instance; 18 | FirebaseUser _currentUser; 19 | String _userDocumentId; 20 | 21 | bool _isLoading = false; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return new Scaffold( 31 | backgroundColor: Colors.white, 32 | appBar: new AppBar( 33 | leading: new BackButton(), 34 | title: new Text('Contacts'), 35 | ), 36 | floatingActionButton: new FloatingActionButton( 37 | onPressed: _onFabClicked, 38 | child: new Icon(Icons.add), 39 | ), 40 | bottomNavigationBar: BottomNavigationBar( 41 | items: [ 42 | BottomNavigationBarItem(icon: Icon(Icons.event), title: Text('Events')), 43 | BottomNavigationBarItem(icon: Icon(Icons.contacts), title: Text('Contacts')), 44 | ], 45 | currentIndex: 1, 46 | fixedColor: Colors.deepPurple, 47 | onTap: _onBottomBarItemTapped, 48 | ), 49 | body: _isLoading ? new Center(child: new CircularProgressIndicator()) : FutureBuilder( 50 | future: _getUserContacts(), 51 | builder: (BuildContext context, AsyncSnapshot snapshot) { 52 | if (snapshot.hasError) 53 | return new Text('${snapshot.error}'); 54 | if (snapshot?.data?.documents?.length == 0) { 55 | return new Center( 56 | child: Text('No data available...', style: Theme.of(context).textTheme.title), 57 | ); 58 | } 59 | switch (snapshot.connectionState) { 60 | case ConnectionState.waiting: 61 | return new Center(child: new CircularProgressIndicator()); 62 | default: 63 | return _buildListView(snapshot); 64 | } 65 | }, 66 | ), 67 | ); 68 | } 69 | 70 | Future _getUserDocumentId() async { 71 | QuerySnapshot snapshot = await Firestore.instance.collection(Constants.usersCollectionId) 72 | .where('email', isEqualTo: _currentUser.email).getDocuments(); 73 | 74 | _userDocumentId = snapshot?.documents?.elementAt(0)?.documentID; 75 | 76 | return _userDocumentId; 77 | } 78 | 79 | Future _getUserContacts() async { 80 | _currentUser = await _auth.currentUser(); 81 | await _getUserDocumentId(); 82 | QuerySnapshot snapshot = await Firestore.instance.collection(Constants.usersCollectionId) 83 | .document(_userDocumentId) 84 | .collection(Constants.calendarContactsCollectionId) 85 | .getDocuments(); 86 | 87 | return snapshot; 88 | } 89 | 90 | ListView _buildListView (AsyncSnapshot snapshot) { 91 | return ListView.builder( 92 | itemCount: snapshot?.data?.documents?.length ?? 0, 93 | itemBuilder: (BuildContext context, int index) { 94 | final contactDocument = snapshot?.data?.documents?.elementAt(index); 95 | final String _name = contactDocument.data['name']; 96 | return ListTile( 97 | onTap: () { 98 | Navigator.push(context, MaterialPageRoute(builder: (_) { 99 | return ContactDetails(name: _name, userDocumentId: _userDocumentId, contactDocumentId: contactDocument?.documentID); 100 | })); 101 | }, 102 | leading: CircleAvatar( 103 | child: Text(_name.length > 1 104 | ? _name?.substring(0, 2) 105 | : "")), 106 | title: Hero(tag: _name, child: Text(_name, style: Theme.of(context).textTheme.title)), 107 | trailing: new IconButton( 108 | iconSize: 30.0, 109 | padding: EdgeInsets.all(5.0), 110 | icon: new Icon(Icons.delete), 111 | color: Colors.black, 112 | onPressed: () => _deleteContact(contactDocument)), 113 | ); 114 | }, 115 | ); 116 | } 117 | 118 | Future _onBottomBarItemTapped(int index) async { 119 | switch(index) { 120 | case 0: 121 | Navigator.pushNamed(context, '/calendar'); 122 | break; 123 | case 1: 124 | break; 125 | } 126 | } 127 | 128 | void _onFabClicked() { 129 | Navigator.push(context, MaterialPageRoute(builder: (_) { 130 | return ContactCreator(userDocumentId: _userDocumentId); 131 | })); 132 | } 133 | 134 | void _deleteContact(DocumentSnapshot contactDocument) async { 135 | setState(() { 136 | _isLoading = true; 137 | }); 138 | 139 | CloudFunctions.instance.call( 140 | functionName: 'deleteContactAndSubCollections', 141 | parameters: { 142 | 'userDocumentId': _userDocumentId, 143 | 'contactDocumentId': contactDocument.documentID 144 | }) 145 | .whenComplete( () { 146 | setState(() { 147 | _isLoading = false; 148 | }); 149 | }) 150 | .catchError( (e) { 151 | print('Error: ' + e.toString()); 152 | setState(() { 153 | _isLoading = false; 154 | }); 155 | }); 156 | } 157 | } -------------------------------------------------------------------------------- /lib/event_creator.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'dart:async'; 4 | import 'package:firebase_auth/firebase_auth.dart'; 5 | 6 | import 'package:datetime_picker_formfield/datetime_picker_formfield.dart'; 7 | import 'package:flutter_widget_app/models/event_model.dart'; 8 | import 'package:intl/intl.dart'; 9 | 10 | class EventData { 11 | String title = ''; 12 | DateTime time; 13 | String summary = ''; 14 | } 15 | 16 | class EventCreator extends StatefulWidget { 17 | final Event _event; 18 | 19 | @override 20 | State createState() { 21 | return new EventCreatorState(); 22 | } 23 | 24 | EventCreator(this._event) { 25 | createState(); 26 | } 27 | } 28 | 29 | class EventCreatorState extends State { 30 | final dateFormat = DateFormat("MMMM d, yyyy 'at' h:mma"); 31 | final FirebaseAuth _auth = FirebaseAuth.instance; 32 | final GlobalKey _formKey = new GlobalKey(); 33 | EventData _eventData = new EventData(); 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | 38 | final titleWidget = new TextFormField( 39 | keyboardType: TextInputType.text, 40 | decoration: new InputDecoration( 41 | hintText: 'Event Name', 42 | labelText: 'Event Title', 43 | contentPadding: EdgeInsets.all(16.0), 44 | border: OutlineInputBorder( 45 | borderRadius: BorderRadius.circular(8.0), 46 | ) 47 | ), 48 | initialValue: widget._event != null ? widget._event.title : '', 49 | style: Theme.of(context).textTheme.headline, 50 | validator: this._validateTitle, 51 | onSaved: (String value) => this._eventData.title = value, 52 | ); 53 | 54 | final notesWidget = new TextFormField( 55 | keyboardType: TextInputType.multiline, 56 | maxLines: 4, 57 | decoration: InputDecoration( 58 | hintText: 'Notes', 59 | labelText: 'Enter your notes here', 60 | contentPadding: EdgeInsets.all(16.0), 61 | border: OutlineInputBorder( 62 | borderRadius: BorderRadius.circular(8.0) 63 | ) 64 | ), 65 | initialValue: widget._event != null ? widget._event.summary : '', 66 | style: Theme.of(context).textTheme.headline, 67 | onSaved: (String value) => this._eventData.summary = value, 68 | ); 69 | 70 | return new Scaffold( 71 | appBar: new AppBar( 72 | leading: new BackButton(), 73 | title: new Text('Create New Event'), 74 | actions: [ 75 | new Container( 76 | alignment: Alignment.center, 77 | padding: EdgeInsets.all(15.0), 78 | child: new InkWell( 79 | child: new Text( 80 | 'SAVE', 81 | style: TextStyle( 82 | fontSize: 20.0), 83 | ), 84 | onTap: () => _saveNewEvent(context), 85 | ), 86 | ) 87 | ], 88 | ), 89 | body: new Form( 90 | key: this._formKey, 91 | child: new Container( 92 | padding: EdgeInsets.all(10.0), 93 | child: new Column( 94 | children: [ 95 | titleWidget, 96 | SizedBox(height: 16.0), 97 | new DateTimePickerFormField( 98 | initialDate: widget._event != null ? widget._event.time : DateTime.now(), 99 | initialValue: widget._event != null ? widget._event.time : DateTime.now(), 100 | inputType: InputType.both, 101 | format: dateFormat, 102 | keyboardType: TextInputType.datetime, 103 | style: TextStyle(fontSize: 20.0, color: Colors.black), 104 | editable: true, 105 | decoration: InputDecoration( 106 | labelText: 'Event Date', 107 | hintText: 'November 1, 2018 at 5:00PM', 108 | contentPadding: EdgeInsets.all(20.0), 109 | border: OutlineInputBorder( 110 | borderRadius: BorderRadius.circular(8.0) 111 | ) 112 | ), 113 | autovalidate: false, 114 | validator: this._validateDate, 115 | onSaved: (DateTime value) => this._eventData.time = value, 116 | ), 117 | SizedBox(height: 16.0), 118 | notesWidget, 119 | ], 120 | ), 121 | ) 122 | 123 | ), 124 | ); 125 | } 126 | 127 | String _validateTitle(String value) { 128 | if (value.isEmpty) { 129 | return 'Please enter a valid title.'; 130 | } else { 131 | return null; 132 | } 133 | } 134 | 135 | String _validateDate(DateTime value) { 136 | if ( (value != null) 137 | && (value.day >= 1 && value.day <= 31) 138 | && (value.month >= 1 && value.month <= 12) 139 | && (value.year >= 2015 && value.year <= 3000)) { 140 | return null; 141 | } else { 142 | return 'Please enter a valid event date.'; 143 | } 144 | } 145 | 146 | Future _saveNewEvent(BuildContext context) async { 147 | FirebaseUser currentUser = await _auth.currentUser(); 148 | print('current user: $currentUser'); 149 | 150 | if (currentUser != null && this._formKey.currentState.validate()) { 151 | _formKey.currentState.save(); // Save our form now. 152 | 153 | Firestore.instance.collection('calendar_events').document(widget._event != null ? widget._event.documentId : null) 154 | .setData({'name': _eventData.title, 'summary': _eventData.summary, 155 | 'time': _eventData.time, 'email': currentUser.email}); 156 | 157 | Navigator.maybePop(context); 158 | } else { 159 | print('Error validating data and saving to firestore.'); 160 | } 161 | } 162 | 163 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.8" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.0.4" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.2" 25 | cloud_firestore: 26 | dependency: "direct main" 27 | description: 28 | name: cloud_firestore 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "0.9.5+2" 32 | cloud_functions: 33 | dependency: "direct main" 34 | description: 35 | name: cloud_functions 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "0.1.2" 53 | datetime_picker_formfield: 54 | dependency: "direct main" 55 | description: 56 | name: datetime_picker_formfield 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "0.1.8" 60 | firebase_auth: 61 | dependency: "direct main" 62 | description: 63 | name: firebase_auth 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.8.0+3" 67 | firebase_core: 68 | dependency: "direct main" 69 | description: 70 | name: firebase_core 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.3.0" 74 | firebase_messaging: 75 | dependency: "direct main" 76 | description: 77 | name: firebase_messaging 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "4.0.0+1" 81 | flutter: 82 | dependency: "direct main" 83 | description: flutter 84 | source: sdk 85 | version: "0.0.0" 86 | flutter_auth_buttons: 87 | dependency: "direct main" 88 | description: 89 | name: flutter_auth_buttons 90 | url: "https://pub.dartlang.org" 91 | source: hosted 92 | version: "0.3.1" 93 | flutter_test: 94 | dependency: "direct dev" 95 | description: flutter 96 | source: sdk 97 | version: "0.0.0" 98 | google_sign_in: 99 | dependency: "direct main" 100 | description: 101 | name: google_sign_in 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "4.0.1+1" 105 | intl: 106 | dependency: transitive 107 | description: 108 | name: intl 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.15.7" 112 | matcher: 113 | dependency: transitive 114 | description: 115 | name: matcher 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "0.12.3+1" 119 | meta: 120 | dependency: transitive 121 | description: 122 | name: meta 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "1.1.6" 126 | path: 127 | dependency: transitive 128 | description: 129 | name: path 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "1.6.2" 133 | pedantic: 134 | dependency: transitive 135 | description: 136 | name: pedantic 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "1.4.0" 140 | platform: 141 | dependency: transitive 142 | description: 143 | name: platform 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "2.2.0" 147 | quiver: 148 | dependency: transitive 149 | description: 150 | name: quiver 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "2.0.1" 154 | sky_engine: 155 | dependency: transitive 156 | description: flutter 157 | source: sdk 158 | version: "0.0.99" 159 | source_span: 160 | dependency: transitive 161 | description: 162 | name: source_span 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.5.4" 166 | stack_trace: 167 | dependency: transitive 168 | description: 169 | name: stack_trace 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "1.9.3" 173 | stream_channel: 174 | dependency: transitive 175 | description: 176 | name: stream_channel 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "1.6.8" 180 | string_scanner: 181 | dependency: transitive 182 | description: 183 | name: string_scanner 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.0.4" 187 | term_glyph: 188 | dependency: transitive 189 | description: 190 | name: term_glyph 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.1.0" 194 | test_api: 195 | dependency: transitive 196 | description: 197 | name: test_api 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "0.2.2" 201 | typed_data: 202 | dependency: transitive 203 | description: 204 | name: typed_data 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "1.1.6" 208 | vector_math: 209 | dependency: transitive 210 | description: 211 | name: vector_math 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "2.0.8" 215 | sdks: 216 | dart: ">=2.1.0 <3.0.0" 217 | flutter: ">=0.2.4 <2.0.0" 218 | -------------------------------------------------------------------------------- /lib/event_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:firebase_auth/firebase_auth.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_widget_app/event_creator.dart'; 7 | import 'package:flutter_widget_app/models/event_model.dart'; 8 | import 'package:intl/intl.dart'; 9 | 10 | class EventsView extends StatefulWidget { 11 | final DateTime _eventDate; 12 | 13 | EventsView(DateTime date) : _eventDate = date; 14 | 15 | @override 16 | State createState() { 17 | return EventsViewState(_eventDate); 18 | } 19 | } 20 | 21 | class EventsViewState extends State { 22 | final FirebaseAuth _auth = FirebaseAuth.instance; 23 | final DateTime _eventDate; 24 | 25 | EventsViewState(DateTime date) : _eventDate = date; 26 | 27 | Future _getEvents() async { 28 | FirebaseUser currentUser = await _auth.currentUser(); 29 | 30 | if (currentUser != null) { 31 | QuerySnapshot events = await Firestore.instance 32 | .collection('calendar_events') 33 | .where('time', isGreaterThan: new DateTime(_eventDate.year, _eventDate.month, _eventDate.day-1, 23, 59, 59)) 34 | .where('time', isLessThan: new DateTime(_eventDate.year, _eventDate.month, _eventDate.day+1)) 35 | .where('email', isEqualTo: currentUser.email) 36 | .getDocuments(); 37 | 38 | return events; 39 | } else { 40 | return null; 41 | } 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return new Scaffold( 47 | appBar: new AppBar( 48 | leading: new BackButton(), 49 | title: new Text(_eventDate.month.toString() + '/' + _eventDate.day.toString() 50 | + '/' + _eventDate.year.toString() + ' Events'), 51 | ), 52 | floatingActionButton: new FloatingActionButton( 53 | onPressed: _onFabClicked, 54 | child: new Icon(Icons.add), 55 | ), 56 | body: FutureBuilder( 57 | future: _getEvents(), 58 | builder: (BuildContext context, AsyncSnapshot snapshot) { 59 | switch (snapshot.connectionState) { 60 | case ConnectionState.none: 61 | case ConnectionState.waiting: 62 | return new LinearProgressIndicator(); 63 | case ConnectionState.done: 64 | default: 65 | if (snapshot.hasError) 66 | return new Text('Error: ${snapshot.error}'); 67 | else { 68 | return ListView( 69 | children: snapshot.data.documents.map((document) { 70 | DateTime _eventTime = document.data['time']; 71 | var eventDateFormatter = new DateFormat("MMMM d, yyyy 'at' h:mma"); 72 | 73 | return new GestureDetector( 74 | onTap: () => _onCardClicked(document), 75 | child: new Card( 76 | color: Colors.amberAccent, 77 | elevation: 10.0, 78 | shape: Border.all(color: Colors.black), 79 | child: new Row( 80 | children: [ 81 | new Expanded( 82 | child: 83 | new Column( 84 | crossAxisAlignment: CrossAxisAlignment.start, 85 | children: [ 86 | new Container( 87 | padding: EdgeInsets.all(10.0), 88 | child: new Text('Event: ' + document.data['name'], 89 | style: Theme.of(context).textTheme.headline, 90 | ), 91 | ), 92 | new Container( 93 | padding: EdgeInsets.all(10.0), 94 | child: new Text('Time: ' + eventDateFormatter.format(_eventTime), 95 | style: Theme.of(context).textTheme.headline 96 | ), 97 | ), 98 | new Container( 99 | padding: EdgeInsets.all(10.0), 100 | child: new Text('Summary: ' + document.data['summary'], 101 | style: Theme.of(context).textTheme.headline 102 | ), 103 | ), 104 | ], 105 | ), 106 | ), 107 | new Container( 108 | child: new IconButton( 109 | iconSize: 30.0, 110 | padding: EdgeInsets.all(5.0), 111 | icon: new Icon(Icons.delete), 112 | onPressed: () => _deleteEvent(document)) 113 | ), 114 | 115 | ], 116 | ), 117 | ) 118 | ); 119 | }).toList(), 120 | ); 121 | } 122 | } 123 | } 124 | ), 125 | ); 126 | } 127 | 128 | void _onCardClicked(DocumentSnapshot document) { 129 | Event event = new Event(document.data['name'], document.data['summary'], 130 | document.data['time'], document.documentID); 131 | Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) 132 | => new EventCreator(event))); 133 | } 134 | 135 | 136 | void _deleteEvent(DocumentSnapshot document) { 137 | setState(() { 138 | Firestore.instance.collection('calendar_events').document(document.documentID).delete(); 139 | }); 140 | } 141 | 142 | void _onFabClicked() { 143 | DateTime _createDateTime = new DateTime(_eventDate.year, _eventDate.month, _eventDate.day, 144 | DateTime.now().hour, DateTime.now().minute); 145 | 146 | Event _event = new Event("", "",_createDateTime, null); 147 | 148 | Navigator.push(context, MaterialPageRoute( 149 | builder: (context) => EventCreator(_event) 150 | ) 151 | ); 152 | } 153 | } -------------------------------------------------------------------------------- /lib/sign_in_vew.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_widget_app/global_contants.dart'; 5 | 6 | class LoginData { 7 | String email = ''; 8 | String password = ''; 9 | } 10 | 11 | class SignInPage extends StatefulWidget { 12 | @override 13 | State createState() => new _SignInPageState(); 14 | } 15 | 16 | class _SignInPageState extends State { 17 | final FirebaseAuth _auth = FirebaseAuth.instance; 18 | final GlobalKey _formKey = new GlobalKey(); 19 | LoginData _data = new LoginData(); 20 | bool _isLoading = false; 21 | 22 | String validateEmail(String value) { 23 | if (value.isEmpty || !value.contains('@')) { 24 | return 'The E-mail Address must be a valid email address.'; 25 | } 26 | return null; 27 | } 28 | 29 | String validatePassword(String value) { 30 | if (value.length < 6) { 31 | return 'The Password must be at least 6 characters.'; 32 | } 33 | return null; 34 | } 35 | 36 | Future signInWithEmail() async { 37 | FirebaseUser user; 38 | 39 | // First validate form. 40 | if (this._formKey.currentState.validate()) { 41 | _formKey.currentState.save(); // Save our form now. 42 | 43 | setState(() { 44 | _isLoading = true; 45 | }); 46 | 47 | try { 48 | user = await _auth.signInWithEmailAndPassword(email: _data.email, 49 | password: _data.password); 50 | } catch (error) { 51 | showErrorDialog(error); 52 | } finally { 53 | assert(user != null); 54 | assert(await user.getIdToken() != null); 55 | 56 | final FirebaseUser currentUser = await _auth.currentUser(); 57 | assert(user.uid == currentUser.uid); 58 | 59 | print('signInEmail succeeded'); 60 | setState(() { 61 | _isLoading = false; 62 | }); 63 | 64 | // Navigate to main calendar view 65 | _navigateToCalendarView(); 66 | } 67 | } 68 | } 69 | 70 | void _navigateToCalendarView() { 71 | Navigator.of(context).pushNamedAndRemoveUntil(Constants.calendarRoute, 72 | (Route route) => false); 73 | } 74 | 75 | Future signUpWithEmail() async { 76 | FirebaseUser user; 77 | 78 | if (this._formKey.currentState.validate()) { 79 | _formKey.currentState.save(); 80 | 81 | setState(() { 82 | _isLoading = true; 83 | }); 84 | 85 | try { 86 | user = await _auth.createUserWithEmailAndPassword( 87 | email: _data.email, password: _data.password); 88 | } catch (error) { 89 | showErrorDialog(error); 90 | } finally { 91 | assert(user != null); 92 | assert(await user.getIdToken() != null); 93 | 94 | final FirebaseUser currentUser = await _auth.currentUser(); 95 | assert(user.uid == currentUser.uid); 96 | 97 | print('signInEmail succeeded'); 98 | 99 | // Add user to the 'users' collection 100 | await Firestore.instance.collection('users').document() 101 | .setData({'email': currentUser.email, 'token': ""}); 102 | 103 | setState(() { 104 | _isLoading = false; 105 | }); 106 | 107 | // Navigate to main calendar view 108 | _navigateToCalendarView(); 109 | } 110 | } 111 | } 112 | 113 | void showErrorDialog(error) { 114 | showDialog( 115 | context: context, 116 | barrierDismissible: false, 117 | builder: (BuildContext context) { 118 | return new AlertDialog( 119 | title: new Text('Sign Up Error'), 120 | content: new Text(error.message), 121 | actions: [ 122 | new FlatButton( 123 | onPressed: () { 124 | setState(() { 125 | _isLoading = false; 126 | }); 127 | Navigator.of(context).pop(true); 128 | }, 129 | child: new Text('OK') 130 | ) 131 | ], 132 | ); 133 | } 134 | ); 135 | } 136 | 137 | @override 138 | Widget build(BuildContext context) { 139 | final emailWidget = new TextFormField( 140 | keyboardType: TextInputType.emailAddress, 141 | decoration: new InputDecoration( 142 | hintText: 'email@gmail.com', 143 | labelText: 'Email address', 144 | border: OutlineInputBorder( 145 | borderRadius: BorderRadius.circular(32.0) 146 | ) 147 | ), 148 | style: TextStyle(fontSize: 20.0, color: Colors.black), 149 | validator: this.validateEmail, 150 | onSaved: (String value) { 151 | this._data.email = value; 152 | }, 153 | ); 154 | 155 | final passwordWidget = new TextFormField( 156 | obscureText: true, 157 | decoration: new InputDecoration( 158 | hintText: 'Password', 159 | labelText: 'Please enter your password', 160 | border: OutlineInputBorder( 161 | borderRadius: BorderRadius.circular(32.0) 162 | ) 163 | ), 164 | style: TextStyle(fontSize: 20.0, color: Colors.black), 165 | validator: this.validatePassword, 166 | onSaved: (String value) { 167 | this._data.password = value; 168 | }, 169 | ); 170 | 171 | final loginButton = new RaisedButton( 172 | color: Colors.lightBlueAccent, 173 | onPressed: () { 174 | this.signInWithEmail(); 175 | }, 176 | child: new Text('Login', style: new TextStyle(fontSize: 24.0, color: Colors.white)) 177 | ); 178 | 179 | final signUpButton = new RaisedButton( 180 | color: Colors.lightBlueAccent, 181 | onPressed: this.signUpWithEmail, 182 | child: new Text('Sign Up', style: new TextStyle(fontSize: 24.0, color: Colors.white)) 183 | ); 184 | 185 | final resetPasswordText = new GestureDetector( 186 | onTap: () { 187 | _formKey.currentState.save(); 188 | String resetMessage; 189 | if (_data.email.isEmpty) { 190 | resetMessage = 'Please enter a email address.'; 191 | } else { 192 | resetMessage = 'Reset password for ' + _data.email; 193 | } 194 | 195 | showDialog( 196 | context: context, 197 | barrierDismissible: false, 198 | builder: (BuildContext context) { 199 | return new AlertDialog( 200 | title: new Text('Reset Password'), 201 | content: new Text(resetMessage), 202 | actions: [ 203 | new FlatButton( 204 | onPressed: () { 205 | if (_data.email.isNotEmpty) { 206 | _auth.sendPasswordResetEmail(email: _data.email); 207 | } 208 | Navigator.of(context).pop(true); 209 | }, 210 | child: new Text('OK') 211 | ), 212 | new FlatButton( 213 | onPressed: () { 214 | Navigator.of(context).pop(true); 215 | }, 216 | child: new Text("CANCEL"), 217 | ) 218 | ], 219 | ); 220 | } 221 | ); 222 | }, 223 | child: new Text('Reset Password',textAlign: TextAlign.center, 224 | style: new TextStyle(fontSize: 24.0, color: Colors.blue), 225 | ), 226 | ); 227 | 228 | final loginImage = new Image.asset('assets/calendar.png', 229 | height: 128.0, 230 | ); 231 | 232 | final loadingSpinner = new Center( 233 | heightFactor: null, 234 | widthFactor: null, 235 | child: new CircularProgressIndicator(), 236 | ); 237 | 238 | return new Scaffold( 239 | backgroundColor: Colors.white, 240 | appBar: new AppBar( 241 | leading: new BackButton(), 242 | title: new Text('Events Calendar'), 243 | ), 244 | body: Padding( 245 | padding: const EdgeInsets.only(left: 24.0, right: 24.0), 246 | child: SafeArea( 247 | child: Column( 248 | crossAxisAlignment: CrossAxisAlignment.stretch, 249 | mainAxisAlignment: MainAxisAlignment.start, 250 | children: [ 251 | SizedBox(height: 8.0), 252 | loginImage, 253 | new Form( 254 | key: this._formKey, 255 | child: new Column( 256 | children: [ 257 | emailWidget, 258 | SizedBox(height: 8.0), 259 | passwordWidget, 260 | ], 261 | ), 262 | ), 263 | SizedBox(height: 8.0), 264 | _isLoading ? loadingSpinner : 265 | Column( 266 | crossAxisAlignment: CrossAxisAlignment.stretch, 267 | children: [ 268 | loginButton, 269 | signUpButton, 270 | ], 271 | ), 272 | SizedBox(height: 4.0), 273 | resetPasswordText, 274 | ], 275 | ), 276 | ), 277 | ) 278 | ); 279 | } 280 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:firebase_auth/firebase_auth.dart'; 4 | import 'package:firebase_messaging/firebase_messaging.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:cloud_firestore/cloud_firestore.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:flutter_widget_app/global_contants.dart'; 9 | import 'package:flutter_widget_app/sign_in_vew.dart'; 10 | 11 | import 'splash_screen.dart'; 12 | import 'event_creator.dart'; 13 | import 'event_view.dart'; 14 | import 'contacts_view.dart'; 15 | 16 | enum _AppBarMenu {logout} 17 | 18 | void main() { 19 | SystemChrome.setPreferredOrientations([ 20 | DeviceOrientation.portraitUp, 21 | DeviceOrientation.portraitDown 22 | ]); 23 | runApp(new MyApp()); 24 | } 25 | 26 | class MyApp extends StatelessWidget { 27 | final FirebaseAuth _auth = FirebaseAuth.instance; 28 | 29 | // This widget is the root of your application. 30 | @override 31 | Widget build(BuildContext context) { 32 | return new MaterialApp( 33 | title: 'Events Calendar', 34 | theme: new ThemeData( 35 | primarySwatch: Colors.blue, 36 | ), 37 | home: _loadHomeScreen(), 38 | routes: { 39 | '/splash': (context) => SplashPage(), 40 | '/calendar': (context) => MyApp(), 41 | '/event_creator': (context) => EventCreator(null), 42 | '/calendar_contacts': (context) => CalendarContacts(), 43 | '/sign_in_page': (context) => SignInPage(), 44 | }, 45 | ); 46 | } 47 | 48 | Widget _loadHomeScreen() { 49 | return FutureBuilder( 50 | future: _auth.currentUser(), 51 | builder: (BuildContext context, AsyncSnapshot snapshot) { 52 | switch(snapshot.connectionState) { 53 | case ConnectionState.none: 54 | case ConnectionState.waiting: 55 | return CircularProgressIndicator(); 56 | default: 57 | if(snapshot.hasError) { 58 | return Text('Error: ${snapshot.error}'); 59 | } else { 60 | if(snapshot.data == null) 61 | return SplashPage(); 62 | else 63 | return HomePage(); 64 | } 65 | } 66 | } 67 | ); 68 | } 69 | } 70 | 71 | class HomePage extends StatefulWidget { 72 | @override 73 | State createState() { 74 | return CalendarState(); 75 | } 76 | } 77 | 78 | class CalendarState extends State { 79 | final FirebaseAuth _auth = FirebaseAuth.instance; 80 | final FirebaseMessaging _firebaseMessaging = new FirebaseMessaging(); 81 | DateTime _dateTime; 82 | QuerySnapshot _userEventSnapshot; 83 | int _beginMonthPadding=0; 84 | 85 | CalendarState() { 86 | _dateTime = DateTime.now(); 87 | setMonthPadding(); 88 | } 89 | 90 | @override 91 | void initState() { 92 | super.initState(); 93 | 94 | _firebaseMessaging.configure( 95 | onMessage: (Map message) async { 96 | print("******** - onMessage: $message"); 97 | }, 98 | onLaunch: (Map message) async { 99 | print("******** - onLaunch: $message"); 100 | }, 101 | onResume: (Map message) async { 102 | print("******** - onResume: $message"); 103 | }, 104 | ); 105 | 106 | _firebaseMessaging.requestNotificationPermissions( 107 | const IosNotificationSettings(sound: true, badge: true, alert: true)); 108 | _firebaseMessaging.onIosSettingsRegistered.listen((IosNotificationSettings settings) { 109 | print("Settings registered: $settings"); 110 | }); 111 | 112 | _firebaseMessaging.getToken().then((String token) async { 113 | assert(token != null); 114 | print('push token: ' + token); 115 | 116 | FirebaseUser user = await FirebaseAuth.instance.currentUser(); 117 | QuerySnapshot snapshot = await Firestore.instance.collection('users') 118 | .where('email', isEqualTo: user.email).getDocuments(); 119 | 120 | snapshot.documents.forEach((doc) { 121 | Firestore.instance.collection('users').document(doc.documentID) 122 | .setData({'email': user.email, 'token': token}); 123 | }); 124 | }); 125 | } 126 | 127 | void setMonthPadding() { 128 | _beginMonthPadding = new DateTime(_dateTime.year, _dateTime.month, 1).weekday; 129 | _beginMonthPadding == 7 ? (_beginMonthPadding = 0) : _beginMonthPadding; 130 | } 131 | 132 | Future _getCalendarData() async { 133 | FirebaseUser currentUser = await _auth.currentUser(); 134 | 135 | if (currentUser != null) { 136 | QuerySnapshot userEvents = await Firestore.instance 137 | .collection('calendar_events') 138 | .where( 139 | 'time', isGreaterThanOrEqualTo: new DateTime(_dateTime.year, _dateTime.month)) 140 | .where('email', isEqualTo: currentUser.email) 141 | .getDocuments(); 142 | 143 | _userEventSnapshot = userEvents; 144 | return _userEventSnapshot; 145 | } else { 146 | return null; 147 | } 148 | } 149 | 150 | void _goToToday() { 151 | print("trying to go to the month of today"); 152 | setState(() { 153 | _dateTime = DateTime.now(); 154 | 155 | setMonthPadding(); 156 | }); 157 | } 158 | 159 | void _previousMonthSelected() { 160 | setState(() { 161 | if (_dateTime.month == DateTime.january) 162 | _dateTime = new DateTime(_dateTime.year - 1, DateTime.december); 163 | else 164 | _dateTime = new DateTime(_dateTime.year, _dateTime.month - 1); 165 | 166 | setMonthPadding(); 167 | }); 168 | } 169 | 170 | void _nextMonthSelected() { 171 | setState(() { 172 | if (_dateTime.month == DateTime.december) 173 | _dateTime = new DateTime(_dateTime.year + 1, DateTime.january); 174 | else 175 | _dateTime = new DateTime(_dateTime.year, _dateTime.month + 1); 176 | 177 | setMonthPadding(); 178 | }); 179 | } 180 | 181 | void _onDayTapped(int day) { 182 | Navigator.push(context, new MaterialPageRoute(builder: (BuildContext context) 183 | => new EventsView(new DateTime(_dateTime.year, _dateTime.month, day))) 184 | ); 185 | } 186 | 187 | void _onFabClicked() { 188 | Navigator.pushNamed(context, Constants.eventCreatorRoute); 189 | } 190 | 191 | @override 192 | Widget build(BuildContext context) { 193 | final int numWeekDays = 7; 194 | var size = MediaQuery.of(context).size; 195 | 196 | /*24 is for notification bar on Android*/ 197 | /*28 is for weekday labels of the row*/ 198 | // 55 is for iPhoneX clipping issue. 199 | final double itemHeight = (size.height - kToolbarHeight-kBottomNavigationBarHeight-24-28-55) / 6; 200 | final double itemWidth = size.width / numWeekDays; 201 | 202 | return new Scaffold( 203 | backgroundColor: Colors.white, 204 | appBar: new AppBar( 205 | title: new FittedBox( 206 | fit: BoxFit.contain, 207 | child: new Text( 208 | getMonthName(_dateTime.month) + " " + _dateTime.year.toString(), 209 | ) 210 | ), 211 | actions: [ 212 | IconButton( 213 | icon: Icon( 214 | Icons.today, 215 | color: Colors.white, 216 | ), 217 | onPressed: _goToToday 218 | ), 219 | IconButton( 220 | icon: Icon( 221 | Icons.chevron_left, 222 | color: Colors.white, 223 | ), 224 | onPressed: _previousMonthSelected 225 | ), 226 | IconButton( 227 | icon: Icon( 228 | Icons.chevron_right, 229 | color: Colors.white, 230 | ), 231 | onPressed: _nextMonthSelected 232 | ), 233 | PopupMenuButton<_AppBarMenu>( 234 | onSelected: (_AppBarMenu value) { 235 | _handleAppbarMenu(context, value); 236 | }, 237 | itemBuilder: (BuildContext context) => >[ 238 | const PopupMenuItem( 239 | value: _AppBarMenu.logout, 240 | child: FittedBox( 241 | fit: BoxFit.contain, 242 | child: Text('Logout', textAlign: TextAlign.center,), 243 | ), 244 | ) 245 | ], 246 | ), 247 | ], 248 | ), 249 | floatingActionButton: new FloatingActionButton( 250 | onPressed: _onFabClicked, 251 | child: new Icon(Icons.add), 252 | ), 253 | bottomNavigationBar: BottomNavigationBar( 254 | items: [ 255 | BottomNavigationBarItem(icon: Icon(Icons.event), title: Text('Events')), 256 | BottomNavigationBarItem(icon: Icon(Icons.contacts), title: Text('Contacts')), 257 | ], 258 | currentIndex: 0, 259 | fixedColor: Colors.deepPurple, 260 | onTap: _onBottomBarItemTapped, 261 | ), 262 | body: 263 | new FutureBuilder( 264 | future: _getCalendarData(), 265 | builder: (BuildContext context, AsyncSnapshot snapshot) { 266 | switch (snapshot.connectionState) { 267 | case ConnectionState.none: 268 | case ConnectionState.waiting: 269 | return new LinearProgressIndicator(); 270 | case ConnectionState.done: 271 | return new Column( 272 | children: [ 273 | new Row( 274 | children: [ 275 | new Expanded( 276 | child: new Text('S', 277 | textAlign: TextAlign.center, 278 | style: Theme 279 | .of(context) 280 | .textTheme 281 | .headline)), 282 | new Expanded( 283 | child: new Text('M', 284 | textAlign: TextAlign.center, 285 | style: Theme 286 | .of(context) 287 | .textTheme 288 | .headline)), 289 | new Expanded( 290 | child: new Text('T', 291 | textAlign: TextAlign.center, 292 | style: Theme 293 | .of(context) 294 | .textTheme 295 | .headline)), 296 | new Expanded( 297 | child: new Text('W', 298 | textAlign: TextAlign.center, 299 | style: Theme 300 | .of(context) 301 | .textTheme 302 | .headline)), 303 | new Expanded( 304 | child: new Text('T', 305 | textAlign: TextAlign.center, 306 | style: Theme 307 | .of(context) 308 | .textTheme 309 | .headline)), 310 | new Expanded( 311 | child: new Text('F', 312 | textAlign: TextAlign.center, 313 | style: Theme 314 | .of(context) 315 | .textTheme 316 | .headline)), 317 | new Expanded( 318 | child: new Text('S', 319 | textAlign: TextAlign.center, 320 | style: Theme 321 | .of(context) 322 | .textTheme 323 | .headline)), 324 | ], 325 | mainAxisSize: MainAxisSize.min, 326 | ), 327 | new GridView.count( 328 | crossAxisCount: numWeekDays, 329 | childAspectRatio: (itemWidth / itemHeight), 330 | shrinkWrap: true, 331 | scrollDirection: Axis.vertical, 332 | children: List.generate( 333 | getNumberOfDaysInMonth(_dateTime.month), 334 | (index) { 335 | int dayNumber = index + 1; 336 | return new GestureDetector( 337 | // Used for handling tap on each day view 338 | onTap: () => 339 | _onDayTapped( 340 | dayNumber - _beginMonthPadding), 341 | child: new Container( 342 | margin: const EdgeInsets.all(2.0), 343 | padding: const EdgeInsets.all(1.0), 344 | decoration: new BoxDecoration( 345 | border: new Border.all( 346 | color: Colors.grey)), 347 | child: new Column( 348 | children: [ 349 | buildDayNumberWidget(dayNumber), 350 | buildDayEventInfoWidget(dayNumber) 351 | ], 352 | ))); 353 | }), 354 | ) 355 | ], 356 | ); 357 | break; 358 | default: 359 | if (snapshot.hasError) 360 | return new Text('Error: ${snapshot.error}'); 361 | else 362 | return new Text('Result: ${snapshot.data}'); 363 | } 364 | } 365 | ) 366 | ); 367 | } 368 | 369 | Align buildDayNumberWidget(int dayNumber) { 370 | //print('buildDayNumberWidget, dayNumber: $dayNumber'); 371 | if ((dayNumber-_beginMonthPadding) == DateTime.now().day 372 | && _dateTime.month == DateTime.now().month 373 | && _dateTime.year == DateTime.now().year) { 374 | // Add a circle around the current day 375 | return Align( 376 | alignment: Alignment.topLeft, 377 | child: Container( 378 | width: 35.0, // Should probably calculate these values 379 | height: 35.0, 380 | padding: EdgeInsets.all(5.0), 381 | decoration: BoxDecoration( 382 | shape: BoxShape.circle, 383 | color: Colors.orange, 384 | border: Border.all(), 385 | ), 386 | child: new Text( 387 | (dayNumber - _beginMonthPadding).toString(), 388 | textAlign: TextAlign.center, 389 | style: Theme.of(context).textTheme.title, 390 | ), 391 | ), 392 | ); 393 | } else { 394 | // No circle around the current day 395 | return Align( 396 | alignment: Alignment.topLeft, 397 | child: Container( 398 | width: 35.0, // Should probably calculate these values 399 | height: 35.0, 400 | padding: EdgeInsets.fromLTRB(0.0, 5.0, 0.0, 0.0), 401 | child: new Text( 402 | dayNumber <= _beginMonthPadding ? ' ' : (dayNumber - _beginMonthPadding).toString(), 403 | textAlign: TextAlign.center, 404 | style: Theme.of(context).textTheme.headline, 405 | ), 406 | ), 407 | ); 408 | } 409 | } 410 | 411 | Widget buildDayEventInfoWidget(int dayNumber) { 412 | int eventCount = 0; 413 | DateTime eventDate; 414 | 415 | _userEventSnapshot.documents.forEach((doc) { 416 | eventDate = doc.data['time']; 417 | if (eventDate != null 418 | && eventDate.day == dayNumber - _beginMonthPadding 419 | && eventDate.month == _dateTime.month 420 | && eventDate.year == _dateTime.year) { 421 | eventCount++; 422 | } 423 | }); 424 | 425 | if (eventCount > 0) { 426 | return new Expanded( 427 | child: 428 | FittedBox( 429 | alignment: Alignment.topLeft, 430 | fit: BoxFit.contain, 431 | child: new Text( 432 | "Events:$eventCount", 433 | maxLines: 1, 434 | style: new TextStyle(fontWeight: FontWeight.normal, 435 | background: Paint()..color = Colors.amberAccent), 436 | ), 437 | ), 438 | ); 439 | } else { 440 | return new Container(); 441 | } 442 | } 443 | 444 | int getNumberOfDaysInMonth(final int month) { 445 | int numDays = 28; 446 | 447 | // Months are 1, ..., 12 448 | switch (month) { 449 | case 1: 450 | numDays = 31; 451 | break; 452 | case 2: 453 | numDays = 28; 454 | break; 455 | case 3: 456 | numDays = 31; 457 | break; 458 | case 4: 459 | numDays = 30; 460 | break; 461 | case 5: 462 | numDays = 31; 463 | break; 464 | case 6: 465 | numDays = 30; 466 | break; 467 | case 7: 468 | numDays = 31; 469 | break; 470 | case 8: 471 | numDays = 31; 472 | break; 473 | case 9: 474 | numDays = 30; 475 | break; 476 | case 10: 477 | numDays = 31; 478 | break; 479 | case 11: 480 | numDays = 30; 481 | break; 482 | case 12: 483 | numDays = 31; 484 | break; 485 | default: 486 | numDays = 28; 487 | } 488 | return numDays + _beginMonthPadding; 489 | } 490 | 491 | String getMonthName(final int month) { 492 | // Months are 1, ..., 12 493 | switch (month) { 494 | case 1: 495 | return "January"; 496 | case 2: 497 | return "February"; 498 | case 3: 499 | return "March"; 500 | case 4: 501 | return "April"; 502 | case 5: 503 | return "May"; 504 | case 6: 505 | return "June"; 506 | case 7: 507 | return "July"; 508 | case 8: 509 | return "August"; 510 | case 9: 511 | return "September"; 512 | case 10: 513 | return "October"; 514 | case 11: 515 | return "November"; 516 | case 12: 517 | return "December"; 518 | default: 519 | return "Unknown"; 520 | } 521 | } 522 | 523 | Future _handleAppbarMenu(BuildContext context, _AppBarMenu value) async { 524 | switch(value) { 525 | case _AppBarMenu.logout: 526 | await _auth.signOut(); 527 | Navigator.of(context).pushNamedAndRemoveUntil(Constants.splashRoute, (Route route) => false); 528 | break; 529 | } 530 | } 531 | 532 | Future _onBottomBarItemTapped(int index) async { 533 | switch(index) { 534 | case 0: 535 | break; 536 | case 1: 537 | Navigator.pushNamed(context, Constants.calContactsRoute); 538 | break; 539 | } 540 | } 541 | } 542 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 7EFCCA7E967FE2D3504AEBE1 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0CC0F351F8D57390A80906 /* libPods-Runner.a */; }; 16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 19 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 20 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 21 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 22 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 23 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 24 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 25 | FB0003BF2134EC7F00C6668A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = FB0003BE2134EC7F00C6668A /* GoogleService-Info.plist */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXCopyFilesBuildPhase section */ 29 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 30 | isa = PBXCopyFilesBuildPhase; 31 | buildActionMask = 2147483647; 32 | dstPath = ""; 33 | dstSubfolderSpec = 10; 34 | files = ( 35 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 36 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 37 | ); 38 | name = "Embed Frameworks"; 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXCopyFilesBuildPhase section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 45 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 46 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 47 | 3B0CC0F351F8D57390A80906 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 49 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 50 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 51 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 52 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 53 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 54 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 55 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 56 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 58 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 59 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 60 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 61 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | FB0003BE2134EC7F00C6668A /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 63 | FBAB2AEB21349FC5006446DD /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 72 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 73 | 7EFCCA7E967FE2D3504AEBE1 /* libPods-Runner.a in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | /* End PBXFrameworksBuildPhase section */ 78 | 79 | /* Begin PBXGroup section */ 80 | 63FBEB827465E860322B8E08 /* Pods */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | ); 84 | name = Pods; 85 | sourceTree = ""; 86 | }; 87 | 9740EEB11CF90186004384FC /* Flutter */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | FBAB2AEB21349FC5006446DD /* GoogleService-Info.plist */, 91 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 92 | 3B80C3931E831B6300D905FE /* App.framework */, 93 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 94 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 95 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 96 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 97 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 98 | ); 99 | name = Flutter; 100 | sourceTree = ""; 101 | }; 102 | 97C146E51CF9000F007C117D = { 103 | isa = PBXGroup; 104 | children = ( 105 | 9740EEB11CF90186004384FC /* Flutter */, 106 | 97C146F01CF9000F007C117D /* Runner */, 107 | 97C146EF1CF9000F007C117D /* Products */, 108 | 63FBEB827465E860322B8E08 /* Pods */, 109 | CCC76DA7EF2557599E15D2B6 /* Frameworks */, 110 | ); 111 | sourceTree = ""; 112 | }; 113 | 97C146EF1CF9000F007C117D /* Products */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 97C146EE1CF9000F007C117D /* Runner.app */, 117 | ); 118 | name = Products; 119 | sourceTree = ""; 120 | }; 121 | 97C146F01CF9000F007C117D /* Runner */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 125 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 126 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 127 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 128 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 129 | 97C147021CF9000F007C117D /* Info.plist */, 130 | 97C146F11CF9000F007C117D /* Supporting Files */, 131 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 132 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 133 | FB0003BE2134EC7F00C6668A /* GoogleService-Info.plist */, 134 | ); 135 | path = Runner; 136 | sourceTree = ""; 137 | }; 138 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 97C146F21CF9000F007C117D /* main.m */, 142 | ); 143 | name = "Supporting Files"; 144 | sourceTree = ""; 145 | }; 146 | CCC76DA7EF2557599E15D2B6 /* Frameworks */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 3B0CC0F351F8D57390A80906 /* libPods-Runner.a */, 150 | ); 151 | name = Frameworks; 152 | sourceTree = ""; 153 | }; 154 | /* End PBXGroup section */ 155 | 156 | /* Begin PBXNativeTarget section */ 157 | 97C146ED1CF9000F007C117D /* Runner */ = { 158 | isa = PBXNativeTarget; 159 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 160 | buildPhases = ( 161 | 96BF6C45FF1773811A2325B2 /* [CP] Check Pods Manifest.lock */, 162 | 9740EEB61CF901F6004384FC /* Run Script */, 163 | 97C146EA1CF9000F007C117D /* Sources */, 164 | 97C146EB1CF9000F007C117D /* Frameworks */, 165 | 97C146EC1CF9000F007C117D /* Resources */, 166 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 167 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 168 | 76812AC29728955E04A12981 /* [CP] Embed Pods Frameworks */, 169 | 3CFB28D5C399398F3ACB38F2 /* [CP] Copy Pods Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | ); 175 | name = Runner; 176 | productName = Runner; 177 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 178 | productType = "com.apple.product-type.application"; 179 | }; 180 | /* End PBXNativeTarget section */ 181 | 182 | /* Begin PBXProject section */ 183 | 97C146E61CF9000F007C117D /* Project object */ = { 184 | isa = PBXProject; 185 | attributes = { 186 | LastUpgradeCheck = 0910; 187 | ORGANIZATIONNAME = "The Chromium Authors"; 188 | TargetAttributes = { 189 | 97C146ED1CF9000F007C117D = { 190 | CreatedOnToolsVersion = 7.3.1; 191 | }; 192 | }; 193 | }; 194 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 195 | compatibilityVersion = "Xcode 3.2"; 196 | developmentRegion = English; 197 | hasScannedForEncodings = 0; 198 | knownRegions = ( 199 | en, 200 | Base, 201 | ); 202 | mainGroup = 97C146E51CF9000F007C117D; 203 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 204 | projectDirPath = ""; 205 | projectRoot = ""; 206 | targets = ( 207 | 97C146ED1CF9000F007C117D /* Runner */, 208 | ); 209 | }; 210 | /* End PBXProject section */ 211 | 212 | /* Begin PBXResourcesBuildPhase section */ 213 | 97C146EC1CF9000F007C117D /* Resources */ = { 214 | isa = PBXResourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 218 | FB0003BF2134EC7F00C6668A /* GoogleService-Info.plist in Resources */, 219 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 220 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 221 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 222 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 223 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, 224 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | /* End PBXResourcesBuildPhase section */ 229 | 230 | /* Begin PBXShellScriptBuildPhase section */ 231 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 232 | isa = PBXShellScriptBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | ); 236 | inputPaths = ( 237 | ); 238 | name = "Thin Binary"; 239 | outputPaths = ( 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | shellPath = /bin/sh; 243 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 244 | }; 245 | 3CFB28D5C399398F3ACB38F2 /* [CP] Copy Pods Resources */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputPaths = ( 251 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", 252 | "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseFirestore/gRPCCertificates.bundle", 253 | "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle", 254 | ); 255 | name = "[CP] Copy Pods Resources"; 256 | outputPaths = ( 257 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates.bundle", 258 | "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | shellPath = /bin/sh; 262 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; 263 | showEnvVarsInLog = 0; 264 | }; 265 | 76812AC29728955E04A12981 /* [CP] Embed Pods Frameworks */ = { 266 | isa = PBXShellScriptBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | inputPaths = ( 271 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 272 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", 273 | ); 274 | name = "[CP] Embed Pods Frameworks"; 275 | outputPaths = ( 276 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | shellPath = /bin/sh; 280 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 281 | showEnvVarsInLog = 0; 282 | }; 283 | 96BF6C45FF1773811A2325B2 /* [CP] Check Pods Manifest.lock */ = { 284 | isa = PBXShellScriptBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | ); 288 | inputPaths = ( 289 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 290 | "${PODS_ROOT}/Manifest.lock", 291 | ); 292 | name = "[CP] Check Pods Manifest.lock"; 293 | outputPaths = ( 294 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | shellPath = /bin/sh; 298 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 299 | showEnvVarsInLog = 0; 300 | }; 301 | 9740EEB61CF901F6004384FC /* Run Script */ = { 302 | isa = PBXShellScriptBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | ); 306 | inputPaths = ( 307 | ); 308 | name = "Run Script"; 309 | outputPaths = ( 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | shellPath = /bin/sh; 313 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 314 | }; 315 | /* End PBXShellScriptBuildPhase section */ 316 | 317 | /* Begin PBXSourcesBuildPhase section */ 318 | 97C146EA1CF9000F007C117D /* Sources */ = { 319 | isa = PBXSourcesBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 323 | 97C146F31CF9000F007C117D /* main.m in Sources */, 324 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | /* End PBXSourcesBuildPhase section */ 329 | 330 | /* Begin PBXVariantGroup section */ 331 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 332 | isa = PBXVariantGroup; 333 | children = ( 334 | 97C146FB1CF9000F007C117D /* Base */, 335 | ); 336 | name = Main.storyboard; 337 | sourceTree = ""; 338 | }; 339 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 340 | isa = PBXVariantGroup; 341 | children = ( 342 | 97C147001CF9000F007C117D /* Base */, 343 | ); 344 | name = LaunchScreen.storyboard; 345 | sourceTree = ""; 346 | }; 347 | /* End PBXVariantGroup section */ 348 | 349 | /* Begin XCBuildConfiguration section */ 350 | 97C147031CF9000F007C117D /* Debug */ = { 351 | isa = XCBuildConfiguration; 352 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 353 | buildSettings = { 354 | ALWAYS_SEARCH_USER_PATHS = NO; 355 | CLANG_ANALYZER_NONNULL = YES; 356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 357 | CLANG_CXX_LIBRARY = "libc++"; 358 | CLANG_ENABLE_MODULES = YES; 359 | CLANG_ENABLE_OBJC_ARC = YES; 360 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 361 | CLANG_WARN_BOOL_CONVERSION = YES; 362 | CLANG_WARN_COMMA = YES; 363 | CLANG_WARN_CONSTANT_CONVERSION = YES; 364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 365 | CLANG_WARN_EMPTY_BODY = YES; 366 | CLANG_WARN_ENUM_CONVERSION = YES; 367 | CLANG_WARN_INFINITE_RECURSION = YES; 368 | CLANG_WARN_INT_CONVERSION = YES; 369 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 370 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 372 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 373 | CLANG_WARN_STRICT_PROTOTYPES = YES; 374 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 375 | CLANG_WARN_UNREACHABLE_CODE = YES; 376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 377 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 378 | COPY_PHASE_STRIP = NO; 379 | DEBUG_INFORMATION_FORMAT = dwarf; 380 | ENABLE_STRICT_OBJC_MSGSEND = YES; 381 | ENABLE_TESTABILITY = YES; 382 | GCC_C_LANGUAGE_STANDARD = gnu99; 383 | GCC_DYNAMIC_NO_PIC = NO; 384 | GCC_NO_COMMON_BLOCKS = YES; 385 | GCC_OPTIMIZATION_LEVEL = 0; 386 | GCC_PREPROCESSOR_DEFINITIONS = ( 387 | "DEBUG=1", 388 | "$(inherited)", 389 | ); 390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 392 | GCC_WARN_UNDECLARED_SELECTOR = YES; 393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 394 | GCC_WARN_UNUSED_FUNCTION = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 397 | MTL_ENABLE_DEBUG_INFO = YES; 398 | ONLY_ACTIVE_ARCH = YES; 399 | SDKROOT = iphoneos; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | }; 402 | name = Debug; 403 | }; 404 | 97C147041CF9000F007C117D /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 407 | buildSettings = { 408 | ALWAYS_SEARCH_USER_PATHS = NO; 409 | CLANG_ANALYZER_NONNULL = YES; 410 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 411 | CLANG_CXX_LIBRARY = "libc++"; 412 | CLANG_ENABLE_MODULES = YES; 413 | CLANG_ENABLE_OBJC_ARC = YES; 414 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 415 | CLANG_WARN_BOOL_CONVERSION = YES; 416 | CLANG_WARN_COMMA = YES; 417 | CLANG_WARN_CONSTANT_CONVERSION = YES; 418 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 419 | CLANG_WARN_EMPTY_BODY = YES; 420 | CLANG_WARN_ENUM_CONVERSION = YES; 421 | CLANG_WARN_INFINITE_RECURSION = YES; 422 | CLANG_WARN_INT_CONVERSION = YES; 423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 425 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 426 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 427 | CLANG_WARN_STRICT_PROTOTYPES = YES; 428 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 429 | CLANG_WARN_UNREACHABLE_CODE = YES; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 432 | COPY_PHASE_STRIP = NO; 433 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 434 | ENABLE_NS_ASSERTIONS = NO; 435 | ENABLE_STRICT_OBJC_MSGSEND = YES; 436 | GCC_C_LANGUAGE_STANDARD = gnu99; 437 | GCC_NO_COMMON_BLOCKS = YES; 438 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 439 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 440 | GCC_WARN_UNDECLARED_SELECTOR = YES; 441 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 442 | GCC_WARN_UNUSED_FUNCTION = YES; 443 | GCC_WARN_UNUSED_VARIABLE = YES; 444 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 445 | MTL_ENABLE_DEBUG_INFO = NO; 446 | SDKROOT = iphoneos; 447 | TARGETED_DEVICE_FAMILY = "1,2"; 448 | VALIDATE_PRODUCT = YES; 449 | }; 450 | name = Release; 451 | }; 452 | 97C147061CF9000F007C117D /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 455 | buildSettings = { 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | CURRENT_PROJECT_VERSION = 1; 458 | ENABLE_BITCODE = NO; 459 | FRAMEWORK_SEARCH_PATHS = ( 460 | "$(inherited)", 461 | "$(PROJECT_DIR)/Flutter", 462 | ); 463 | INFOPLIST_FILE = Runner/Info.plist; 464 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 465 | LIBRARY_SEARCH_PATHS = ( 466 | "$(inherited)", 467 | "$(PROJECT_DIR)/Flutter", 468 | ); 469 | PRODUCT_BUNDLE_IDENTIFIER = com.graham.calendar; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | VERSIONING_SYSTEM = "apple-generic"; 472 | }; 473 | name = Debug; 474 | }; 475 | 97C147071CF9000F007C117D /* Release */ = { 476 | isa = XCBuildConfiguration; 477 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 478 | buildSettings = { 479 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 480 | CURRENT_PROJECT_VERSION = 1; 481 | ENABLE_BITCODE = NO; 482 | FRAMEWORK_SEARCH_PATHS = ( 483 | "$(inherited)", 484 | "$(PROJECT_DIR)/Flutter", 485 | ); 486 | INFOPLIST_FILE = Runner/Info.plist; 487 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 488 | LIBRARY_SEARCH_PATHS = ( 489 | "$(inherited)", 490 | "$(PROJECT_DIR)/Flutter", 491 | ); 492 | PRODUCT_BUNDLE_IDENTIFIER = com.graham.calendar; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | VERSIONING_SYSTEM = "apple-generic"; 495 | }; 496 | name = Release; 497 | }; 498 | /* End XCBuildConfiguration section */ 499 | 500 | /* Begin XCConfigurationList section */ 501 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 502 | isa = XCConfigurationList; 503 | buildConfigurations = ( 504 | 97C147031CF9000F007C117D /* Debug */, 505 | 97C147041CF9000F007C117D /* Release */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | 97C147061CF9000F007C117D /* Debug */, 514 | 97C147071CF9000F007C117D /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | /* End XCConfigurationList section */ 520 | }; 521 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 522 | } 523 | --------------------------------------------------------------------------------