Repository: davefaliskie/travel_treasury
Branch: master
Commit: 72149e3bb1bb
Files: 83
Total size: 179.4 KB
Directory structure:
gitextract_mhc07qrs/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── android/
│ ├── app/
│ │ ├── build.gradle
│ │ └── src/
│ │ ├── debug/
│ │ │ └── AndroidManifest.xml
│ │ ├── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin/
│ │ │ │ └── com/
│ │ │ │ └── a1manstartup/
│ │ │ │ └── travel_budget/
│ │ │ │ └── MainActivity.kt
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26/
│ │ │ │ └── ic_launcher.xml
│ │ │ └── values/
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ └── profile/
│ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── settings.gradle
│ └── settings_aar.gradle
├── assets/
│ └── sun_clouds.flr
├── ios/
│ ├── Flutter/
│ │ ├── .last_build_id
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ ├── Flutter.podspec
│ │ ├── Release.xcconfig
│ │ └── flutter_export_environment.sh
│ ├── Podfile
│ ├── Runner/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── LaunchImage.imageset/
│ │ │ ├── Contents.json
│ │ │ └── README.md
│ │ ├── Base.lproj/
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── Runner-Bridging-Header.h
│ │ └── Runner.entitlements
│ ├── Runner.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── Runner.xcscheme
│ └── Runner.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ └── IDEWorkspaceChecks.plist
├── lib/
│ ├── classes/
│ │ └── progress_painter.dart
│ ├── generated/
│ │ └── i18n.dart
│ ├── main.dart
│ ├── models/
│ │ ├── Place.dart
│ │ ├── Trip.dart
│ │ └── User.dart
│ ├── services/
│ │ ├── admob_service.dart
│ │ ├── auth_service.dart
│ │ ├── custom_colors.dart
│ │ └── firebase_service.dart
│ ├── views/
│ │ ├── deposit_view.dart
│ │ ├── detail_trip_view.dart
│ │ ├── edit_notes_view.dart
│ │ ├── first_view.dart
│ │ ├── home_view.dart
│ │ ├── home_widgets/
│ │ │ ├── current_daily_budget.dart
│ │ │ ├── days_until_trip.dart
│ │ │ ├── home_header.dart
│ │ │ ├── notes.dart
│ │ │ ├── percent_saved.dart
│ │ │ ├── saved_vs_needed.dart
│ │ │ ├── travel_type.dart
│ │ │ └── trip_details_card.dart
│ │ ├── navigation_view.dart
│ │ ├── new_trips/
│ │ │ ├── budget_view.dart
│ │ │ ├── date_view.dart
│ │ │ ├── location_view.dart
│ │ │ └── summary_view.dart
│ │ ├── past_trips_view.dart
│ │ ├── profile_view.dart
│ │ └── sign_up_view.dart
│ └── widgets/
│ ├── calculator_widget.dart
│ ├── custom_dialog.dart
│ ├── divider_with_text_widget.dart
│ ├── money_text_field.dart
│ ├── provider_widget.dart
│ ├── rounded_button.dart
│ └── trip_card.dart
├── pubspec.yaml
├── res/
│ └── values/
│ └── strings_en.arb
└── test/
└── widget_test.dart
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['https://1manstartup.com/donate']
================================================
FILE: .gitignore
================================================
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# Visual Studio Code related
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
*google-services.json
*GoogleService-Info.plist
*lib/credentials.dart
================================================
FILE: .metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
channel: stable
project_type: app
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Dave Faliskie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Travel Treasury
[Download The Live App](https://traveltreasury.app)

This repository contains all the code written throughout the 1ManStartup YouTube tutorials for building a travel budget app using Flutter
[View the channel on YouTube](https://www.youtube.com/channel/UC8xcnxN4CyXdPCeUN1eURPg)
## How To Use This Resource
Each episode where code is created or modified will have an associated branch
in this repo. The code in each episode's branch will contain the completed code from that episode
and the branch will remain in that state.
The master branch will contain the most recent version of code, and be considered the "production" version.
This means the master branch will always be the most up to date.
Any questions should be asked in the comments of the relevant video on YouTube.
## Setup Firebase Database
After Episode 15 you will need to configure your own Firebase project. Most importantly you will need to generate and include your own google-services.json file for Android and GoogleServices-Info.plist file for iOS. Full instructions on how to configure Firebase for this project can be found in [Episode 15 on YouTube](https://youtu.be/8L0YWmVYIqU)
================================================
FILE: android/app/build.gradle
================================================
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.firebase.crashlytics'
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdkVersion 28
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
applicationId "com.a1manstartup.travel_budget"
minSdkVersion 21
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// TODO: find a better solution to the DEX reference limit https://developer.android.com/studio/build/multidex
multiDexEnabled true
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:multidex:1.0.3'
}
apply plugin: 'com.google.gms.google-services'
================================================
FILE: android/app/src/debug/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/main/AndroidManifest.xml
================================================
================================================
FILE: android/app/src/main/kotlin/com/a1manstartup/travel_budget/MainActivity.kt
================================================
package com.a1manstartup.travel_budget
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}
================================================
FILE: android/app/src/main/res/drawable/launch_background.xml
================================================
-
================================================
FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: android/app/src/main/res/values/colors.xml
================================================
#57AEAF
================================================
FILE: android/app/src/main/res/values/styles.xml
================================================
================================================
FILE: android/app/src/profile/AndroidManifest.xml
================================================
================================================
FILE: android/build.gradle
================================================
buildscript {
ext.kotlin_version = '1.3.21'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: android/gradle/wrapper/gradle-wrapper.properties
================================================
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
================================================
FILE: android/gradle.properties
================================================
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
================================================
FILE: android/settings.gradle
================================================
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
================================================
FILE: android/settings_aar.gradle
================================================
include ':app'
================================================
FILE: ios/Flutter/.last_build_id
================================================
ff43a8078c18a80d1d983aec4895d6ad
================================================
FILE: ios/Flutter/AppFrameworkInfo.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
App
CFBundleIdentifier
io.flutter.flutter.app
CFBundleInfoDictionaryVersion
6.0
CFBundleName
App
CFBundlePackageType
FMWK
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1.0
MinimumOSVersion
8.0
================================================
FILE: ios/Flutter/Debug.xcconfig
================================================
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
================================================
FILE: ios/Flutter/Flutter.podspec
================================================
#
# NOTE: This podspec is NOT to be published. It is only used as a local source!
#
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'High-performance, high-fidelity mobile apps.'
s.description = <<-DESC
Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
DESC
s.homepage = 'https://flutter.io'
s.license = { :type => 'MIT' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.vendored_frameworks = 'Flutter.framework'
end
================================================
FILE: ios/Flutter/Release.xcconfig
================================================
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
================================================
FILE: ios/Flutter/flutter_export_environment.sh
================================================
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/davefaliskie/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/davefaliskie/Code/travel_budget"
export "FLUTTER_TARGET=/Users/davefaliskie/Code/travel_budget/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
export "FLUTTER_FRAMEWORK_DIR=/Users/davefaliskie/flutter/bin/cache/artifacts/engine/ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=flutter.inspector.structuredErrors%3Dtrue"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.packages"
================================================
FILE: ios/Podfile
================================================
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
================================================
FILE: ios/Runner/AppDelegate.swift
================================================
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
================================================
FILE: ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "launch_screen.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "launch_screen@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "launch_screen@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
================================================
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
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.
================================================
FILE: ios/Runner/Base.lproj/LaunchScreen.storyboard
================================================
================================================
FILE: ios/Runner/Base.lproj/Main.storyboard
================================================
================================================
FILE: ios/Runner/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
Travel Treasury
CFBundlePackageType
APPL
CFBundleShortVersionString
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
CFBundleURLTypes
CFBundleTypeRole
Editor
CFBundleURLSchemes
com.googleusercontent.apps.635382246089-feadgcif5jb704jtq2nir0d6cnh8fu02
CFBundleTypeRole
Editor
CFBundleURLSchemes
com.googleusercontent.apps.635382246089-egv2iuv19s1e0gjiq7rqvngu4c7e7su0
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
GADApplicationIdentifier
ca-app-pub-2334510780816542~6726672523
LSApplicationCategoryType
LSRequiresIPhoneOS
UIBackgroundModes
fetch
remote-notification
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIViewControllerBasedStatusBarAppearance
io.flutter.embedded_views_preview
================================================
FILE: ios/Runner/Runner-Bridging-Header.h
================================================
#import "GeneratedPluginRegistrant.h"
================================================
FILE: ios/Runner/Runner.entitlements
================================================
com.apple.developer.applesignin
Default
================================================
FILE: ios/Runner.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
79B7BE68738E518A2180EBC1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2C587E9A5CE2029DC627BB1 /* Pods_Runner.framework */; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
F661854B22D2FB7A00F470E2 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F661854A22D2FB7900F470E2 /* GoogleService-Info.plist */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
4B222A4CE387272255EFF8F4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
585F8E30EE2A7E3719072D33 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
6D26CDADDD27BBED144A21CE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
A2C587E9A5CE2029DC627BB1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F60F054123AED29C00A7CBC4 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
F661854A22D2FB7900F470E2 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
79B7BE68738E518A2180EBC1 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7AEB469C9BCC6CEA8F744F18 /* Pods */ = {
isa = PBXGroup;
children = (
585F8E30EE2A7E3719072D33 /* Pods-Runner.debug.xcconfig */,
6D26CDADDD27BBED144A21CE /* Pods-Runner.release.xcconfig */,
4B222A4CE387272255EFF8F4 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
sourceTree = "";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
7AEB469C9BCC6CEA8F744F18 /* Pods */,
D77848A49B74BD0A44544A5F /* Frameworks */,
);
sourceTree = "";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
F60F054123AED29C00A7CBC4 /* Runner.entitlements */,
F661854A22D2FB7900F470E2 /* GoogleService-Info.plist */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
);
name = "Supporting Files";
sourceTree = "";
};
D77848A49B74BD0A44544A5F /* Frameworks */ = {
isa = PBXGroup;
children = (
A2C587E9A5CE2029DC627BB1 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
55FBF33E8A6D71BEB6B19B9A /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
68376A1452E274003D21895A /* [CP] Embed Pods Frameworks */,
9CFF9FF5EF81DF04E13A03E3 /* [CP] Copy Pods Resources */,
F61820892586ED4B007F31CA /* Run Script Crashlytics */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0910;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = 8V4XE9BJVC;
LastSwiftMigration = 0910;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
};
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
F661854B22D2FB7A00F470E2 /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
55FBF33E8A6D71BEB6B19B9A /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
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";
showEnvVarsInLog = 0;
};
68376A1452E274003D21895A /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework",
"${BUILT_PRODUCTS_DIR}/BoringSSL-GRPC/openssl_grpc.framework",
"${PODS_ROOT}/../Flutter/Flutter.framework",
"${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework",
"${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
"${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework",
"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
"${BUILT_PRODUCTS_DIR}/abseil/absl.framework",
"${BUILT_PRODUCTS_DIR}/apple_sign_in/apple_sign_in.framework",
"${BUILT_PRODUCTS_DIR}/device_info/device_info.framework",
"${BUILT_PRODUCTS_DIR}/gRPC-C++/grpcpp.framework",
"${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework",
"${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
"${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework",
"${BUILT_PRODUCTS_DIR}/libphonenumber/libphonenumber.framework",
"${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl_grpc.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMAppAuth.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/absl.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/apple_sign_in.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpcpp.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libphonenumber.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
9CFF9FF5EF81DF04E13A03E3 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
"${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
F61820892586ED4B007F31CA /* Run Script Crashlytics */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Run Script Crashlytics";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n${PODS_ROOT}/FirebaseCrashlytics/run\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8V4XE9BJVC;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.a1manstartup.travelBudget1;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 8V4XE9BJVC;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.a1manstartup.travelBudget1;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = On;
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 8V4XE9BJVC;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.a1manstartup.travelBudget1;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = On;
SWIFT_VERSION = 4.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
IDEDidComputeMac32BitWarning
================================================
FILE: ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
================================================
================================================
FILE: ios/Runner.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
IDEDidComputeMac32BitWarning
================================================
FILE: lib/classes/progress_painter.dart
================================================
import 'package:flutter/material.dart';
import 'dart:math';
class ProgressPainter extends CustomPainter {
Color defaultCircleColor;
Color percentageCompletedCircleColor;
double completedPercentage;
double circleWidth;
ProgressPainter(
{this.defaultCircleColor,
this.percentageCompletedCircleColor,
this.completedPercentage,
this.circleWidth});
getPaint(Color color) {
return Paint()
..color = color
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = circleWidth;
}
@override
void paint(Canvas canvas, Size size) {
Paint defaultCirclePaint = getPaint(defaultCircleColor);
Paint progressCirclePaint = getPaint(percentageCompletedCircleColor);
Offset center = Offset(size.width / 2, size.height / 2);
double radius = min(size.width / 2, size.height / 2);
canvas.drawCircle(center, radius, defaultCirclePaint);
double arcAngle = 2 * pi * (completedPercentage / 100);
canvas.drawArc(Rect.fromCircle(center: center, radius: radius), -pi / 2,
arcAngle, false, progressCirclePaint);
}
@override
bool shouldRepaint(CustomPainter painter) {
return true;
}
}
================================================
FILE: lib/generated/i18n.dart
================================================
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: camel_case_types
// ignore_for_file: prefer_single_quotes
// This file is automatically generated. DO NOT EDIT, all your changes would be lost.
class S implements WidgetsLocalizations {
const S();
static S current;
static const GeneratedLocalizationsDelegate delegate =
GeneratedLocalizationsDelegate();
static S of(BuildContext context) => Localizations.of(context, S);
@override
TextDirection get textDirection => TextDirection.ltr;
}
class $en extends S {
const $en();
}
class GeneratedLocalizationsDelegate extends LocalizationsDelegate {
const GeneratedLocalizationsDelegate();
List get supportedLocales {
return const [
Locale("en", ""),
];
}
LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) {
return (List locales, Iterable supported) {
if (locales == null || locales.isEmpty) {
return fallback ?? supported.first;
} else {
return _resolve(locales.first, fallback, supported, withCountry);
}
};
}
LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) {
return (Locale locale, Iterable supported) {
return _resolve(locale, fallback, supported, withCountry);
};
}
@override
Future load(Locale locale) {
final String lang = getLang(locale);
if (lang != null) {
switch (lang) {
case "en":
S.current = const $en();
return SynchronousFuture(S.current);
default:
// NO-OP.
}
}
S.current = const S();
return SynchronousFuture(S.current);
}
@override
bool isSupported(Locale locale) => _isSupported(locale, true);
@override
bool shouldReload(GeneratedLocalizationsDelegate old) => false;
///
/// Internal method to resolve a locale from a list of locales.
///
Locale _resolve(Locale locale, Locale fallback, Iterable supported, bool withCountry) {
if (locale == null || !_isSupported(locale, withCountry)) {
return fallback ?? supported.first;
}
final Locale languageLocale = Locale(locale.languageCode, "");
if (supported.contains(locale)) {
return locale;
} else if (supported.contains(languageLocale)) {
return languageLocale;
} else {
final Locale fallbackLocale = fallback ?? supported.first;
return fallbackLocale;
}
}
///
/// Returns true if the specified locale is supported, false otherwise.
///
bool _isSupported(Locale locale, bool withCountry) {
if (locale != null) {
for (Locale supportedLocale in supportedLocales) {
// Language must always match both locales.
if (supportedLocale.languageCode != locale.languageCode) {
continue;
}
// If country code matches, return this locale.
if (supportedLocale.countryCode == locale.countryCode) {
return true;
}
// If no country requirement is requested, check if this locale has no country.
if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) {
return true;
}
}
}
return false;
}
}
String getLang(Locale l) => l == null
? null
: l.countryCode != null && l.countryCode.isEmpty
? l.languageCode
: l.toString();
================================================
FILE: lib/main.dart
================================================
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:travel_budget/services/custom_colors.dart';
import 'package:travel_budget/views/navigation_view.dart';
import 'package:travel_budget/views/first_view.dart';
import 'package:travel_budget/views/sign_up_view.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'package:travel_budget/services/auth_service.dart';
import 'package:firebase_admob/firebase_admob.dart';
import 'package:travel_budget/services/admob_service.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseAdMob.instance.initialize(appId: AdMobService().getAdMobAppId());
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State with WidgetsBindingObserver {
var colors = CustomColors(WidgetsBinding.instance.window.platformBrightness);
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangePlatformBrightness() {
setState(() {
colors = CustomColors(WidgetsBinding.instance.window.platformBrightness);
});
}
@override
Widget build(BuildContext context) {
return Provider(
auth: AuthService(),
db: FirebaseFirestore.instance,
colors: colors,
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: "Travel Budget App",
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
textTheme: TextTheme(bodyText2: GoogleFonts.quicksand(fontSize: 14.0))),
darkTheme: ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.blue,
textTheme: TextTheme(bodyText2: GoogleFonts.bitter(fontSize: 14.0))),
home: HomeController(),
routes: {
'/home': (BuildContext context) => HomeController(),
'/signUp': (BuildContext context) => SignUpView(authFormType: AuthFormType.signUp),
'/signIn': (BuildContext context) => SignUpView(authFormType: AuthFormType.signIn),
'/anonymousSignIn': (BuildContext context) => SignUpView(authFormType: AuthFormType.anonymous),
'/convertUser': (BuildContext context) => SignUpView(authFormType: AuthFormType.convert),
},
),
);
}
}
class HomeController extends StatelessWidget {
@override
Widget build(BuildContext context) {
final AuthService auth = Provider.of(context).auth;
return StreamBuilder(
stream: auth.onAuthStateChanged,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
final bool signedIn = snapshot.hasData;
return signedIn ? NavigationView() : FirstView();
}
return Container();
},
);
}
}
================================================
FILE: lib/models/Place.dart
================================================
class Place {
String name;
double averageBudget;
String placeId;
Place(
this.name,
this.averageBudget,
this.placeId,
);
}
================================================
FILE: lib/models/Trip.dart
================================================
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:travel_budget/credentials.dart';
class Trip {
String title;
DateTime startDate;
DateTime endDate;
double budget;
Map budgetTypes;
String travelType;
String photoReference;
String notes;
String documentId;
double saved;
List ledger;
Trip(
this.title,
this.startDate,
this.endDate,
this.budget,
this.budgetTypes,
this.travelType
);
// formatting for upload to Firbase when creating the trip
Map toJson() => {
'title': title,
'startDate': startDate,
'endDate': endDate,
'budget': budget,
'budgetTypes': budgetTypes,
'travelType': travelType,
'photoReference': photoReference,
};
// creating a Trip object from a firebase snapshot
Trip.fromSnapshot(DocumentSnapshot snapshot) :
title = snapshot.data()['title'],
startDate = snapshot.data()['startDate'].toDate(),
endDate = snapshot.data()['endDate'].toDate(),
budget = snapshot.data()['budget'],
budgetTypes = snapshot.data()['budgetTypes'],
travelType = snapshot.data()['travelType'],
photoReference = snapshot.data()['photoReference'],
notes = snapshot.data()['notes'],
documentId = snapshot.id,
saved = snapshot.data()['saved'],
ledger = snapshot.data()['ledger'];
Map types({color = Colors.black}) => {
"car": Icon(Icons.directions_car, size: 50, color: color),
"bus": Icon(Icons.directions_bus, size: 50, color: color),
"train": Icon(Icons.train, size: 50, color: color),
"plane": Icon(Icons.airplanemode_active, size: 50, color: color),
"ship": Icon(Icons.directions_boat, size: 50, color: color),
"other": Icon(Icons.directions, size: 50, color: color),
};
// return the google places image
Image getLocationImage() {
final baseUrl = "https://maps.googleapis.com/maps/api/place/photo";
final maxWidth = "1000";
final url = "$baseUrl?maxwidth=$maxWidth&photoreference=$photoReference&key=$PLACES_API_KEY";
return Image.network(url, fit: BoxFit.cover);
}
int getTotalTripDays() {
int total = endDate.difference(startDate).inDays;
if (total < 1) {
total = 1;
}
return total;
}
int getDaysUntilTrip() {
int diff = startDate.difference(DateTime.now()).inDays;
if (diff < 0) {
diff = 0;
}
return diff;
}
int getCurrentDailyBudget() {
if (saved == 0 || saved == null) {
return 0;
} else {
return (saved / getTotalTripDays()).floor();
}
}
Map ledgerItem(String amount, String type) {
var amountDouble = double.parse(amount);
if (type == "spent") {
amountDouble = double.parse("-" + amount);
}
return {
'ledger': FieldValue.arrayUnion([
{
"date": DateTime.now(),
"amount": amountDouble,
},
]),
'saved': FieldValue.increment(amountDouble)
};
}
}
================================================
FILE: lib/models/User.dart
================================================
class User {
String homeCountry;
bool admin;
User(this.homeCountry);
Map toJson() => {
'homeCountry': homeCountry,
'admin': admin,
};
}
================================================
FILE: lib/services/admob_service.dart
================================================
import 'dart:io';
import 'package:firebase_admob/firebase_admob.dart';
import 'package:flutter/material.dart';
class AdMobService {
String getAdMobAppId() {
if (Platform.isIOS) {
return 'ca-app-pub-2334510780816542~6726672523';
} else if (Platform.isAndroid) {
return 'ca-app-pub-2334510780816542~7385148076';
}
return null;
}
static String _getBannerAdId() {
if (Platform.isIOS) {
// return 'ca-app-pub-2334510780816542/6833456062';
return 'ca-app-pub-3940256099942544/2934735716';
} else if (Platform.isAndroid) {
// return 'ca-app-pub-2334510780816542/2993163849';
return "ca-app-pub-3940256099942544/6300978111";
}
return null;
}
String getInterstitialAdId() {
if (Platform.isIOS) {
// return '';
return 'ca-app-pub-3940256099942544/4411468910';
} else if (Platform.isAndroid) {
// return '';
return "ca-app-pub-3940256099942544/1033173712";
}
return null;
}
InterstitialAd getNewTripInterstitial() {
return InterstitialAd(
adUnitId: getInterstitialAdId(),
listener: (MobileAdEvent event) {
print("InterstitialAd event is $event");
},
);
}
static BannerAd _homeBannerAd;
static BannerAd _getHomePageBannerAd() {
return BannerAd(
adUnitId: _getBannerAdId(),
size: AdSize.smartBanner
);
}
static void showHomeBannerAd() {
if ( _homeBannerAd == null ) _homeBannerAd = _getHomePageBannerAd();
_homeBannerAd
..load()
..show(anchorType: AnchorType.bottom, anchorOffset: kBottomNavigationBarHeight);
}
static void hideHomeBannerAd() async {
await _homeBannerAd.dispose();
_homeBannerAd = null;
}
}
================================================
FILE: lib/services/auth_service.dart
================================================
import 'package:apple_sign_in/apple_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
class AuthService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
Stream get onAuthStateChanged => _firebaseAuth.authStateChanges().map(
(User user) => user?.uid,
);
// GET UID
String getCurrentUID() {
return _firebaseAuth.currentUser.uid;
}
// GET CURRENT USER
Future getCurrentUser() async {
return _firebaseAuth.currentUser;
}
getProfileImage() {
if(_firebaseAuth.currentUser.photoURL != null) {
return Image.network(_firebaseAuth.currentUser.photoURL, height: 100, width: 100);
} else {
return Icon(Icons.account_circle, size: 100);
}
}
// Email & Password Sign Up
Future createUserWithEmailAndPassword(
String email, String password, String name) async {
final authResult = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
// Update the username
await updateUserName(name, authResult.user);
return authResult.user.uid;
}
Future updateUserName(String name, User currentUser) async {
await currentUser.updateProfile(displayName: name);
await currentUser.reload();
}
// Email & Password Sign In
Future signInWithEmailAndPassword(
String email, String password) async {
return (await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password))
.user
.uid;
}
// Sign Out
signOut() async {
return await _firebaseAuth.signOut();
}
// Reset Password
Future sendPasswordResetEmail(String email) async {
return _firebaseAuth.sendPasswordResetEmail(email: email);
}
// Create Anonymous User
Future singInAnonymously() {
return _firebaseAuth.signInAnonymously();
}
Future convertUserWithEmail(
String email, String password, String name) async {
final currentUser = _firebaseAuth.currentUser;
final credential =
EmailAuthProvider.credential(email: email, password: password);
await currentUser.linkWithCredential(credential);
await updateUserName(name, currentUser);
}
Future convertWithGoogle() async {
final currentUser = _firebaseAuth.currentUser;
final GoogleSignInAccount account = await _googleSignIn.signIn();
final GoogleSignInAuthentication _googleAuth = await account.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
idToken: _googleAuth.idToken,
accessToken: _googleAuth.accessToken,
);
await currentUser.linkWithCredential(credential);
await updateUserName(_googleSignIn.currentUser.displayName, currentUser);
}
// GOOGLE
Future signInWithGoogle() async {
final GoogleSignInAccount account = await _googleSignIn.signIn();
final GoogleSignInAuthentication _googleAuth = await account.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
idToken: _googleAuth.idToken,
accessToken: _googleAuth.accessToken,
);
return (await _firebaseAuth.signInWithCredential(credential)).user.uid;
}
// APPLE
Future signInWithApple() async {
final AuthorizationResult result = await AppleSignIn.performRequests([
AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
]);
switch (result.status) {
case AuthorizationStatus.authorized:
final AppleIdCredential _auth = result.credential;
final OAuthProvider oAuthProvider =
new OAuthProvider("apple.com");
final AuthCredential credential = oAuthProvider.credential(
idToken: String.fromCharCodes(_auth.identityToken),
accessToken: String.fromCharCodes(_auth.authorizationCode),
);
await _firebaseAuth.signInWithCredential(credential);
// update the user information
if (_auth.fullName != null) {
await _firebaseAuth.currentUser.updateProfile(displayName: "${_auth.fullName.givenName} ${_auth.fullName.familyName}");
}
break;
case AuthorizationStatus.error:
print("Sign In Failed ${result.error.localizedDescription}");
break;
case AuthorizationStatus.cancelled:
print("User Cancled");
break;
}
}
Future createUserWithPhone(String phone, BuildContext context) async {
_firebaseAuth.verifyPhoneNumber(
phoneNumber: phone,
timeout: Duration(seconds: 0),
verificationCompleted: (AuthCredential authCredential) {
_firebaseAuth.signInWithCredential(authCredential).then((UserCredential result){
Navigator.of(context).pop(); // to pop the dialog box
Navigator.of(context).pushReplacementNamed('/home');
}).catchError((e) {
return "error";
});
},
verificationFailed: (FirebaseAuthException exception) {
return "error";
},
codeSent: (String verificationId, [int forceResendingToken]) {
final _codeController = TextEditingController();
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text("Enter Verification Code From Text Message"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [TextField(controller: _codeController)],
),
actions: [
FlatButton(
child: Text("submit"),
textColor: Colors.white,
color: Colors.green,
onPressed: () {
var _credential = PhoneAuthProvider.credential(verificationId: verificationId,
smsCode: _codeController.text.trim());
_firebaseAuth.signInWithCredential(_credential).then((UserCredential result){
Navigator.of(context).pop(); // to pop the dialog box
Navigator.of(context).pushReplacementNamed('/home');
}).catchError((e) {
return "error";
});
},
)
],
),
);
},
codeAutoRetrievalTimeout: (String verificationId) {
verificationId = verificationId;
});
}
}
class NameValidator {
static String validate(String value) {
if (value.isEmpty) {
return "Name can't be empty";
}
if (value.length < 2) {
return "Name must be at least 2 characters long";
}
if (value.length > 50) {
return "Name must be less than 50 characters long";
}
return null;
}
}
class EmailValidator {
static String validate(String value) {
if (value.isEmpty) {
return "Email can't be empty";
}
return null;
}
}
class PasswordValidator {
static String validate(String value) {
if (value.isEmpty) {
return "Password can't be empty";
}
return null;
}
}
================================================
FILE: lib/services/custom_colors.dart
================================================
import 'package:flutter/material.dart';
class CustomColors {
Brightness brightness;
Color text1, primary;
CustomColors(Brightness brightness) {
this.brightness = brightness;
if(brightness == Brightness.dark) {
this.text1 = Color(0xff252223);
this.primary = Color(0xff3c7778);
} else {
this.text1 = Colors.white;
this.primary = Color(0xff57AEAF);
}
}
}
================================================
FILE: lib/services/firebase_service.dart
================================================
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
class FirebaseService {
static Future getNextTrip(context) async {
final uid = Provider.of(context).auth.getCurrentUID();
var snapshot = await FirebaseFirestore.instance
.collection('userData')
.doc(uid)
.collection('trips')
.orderBy('startDate')
.limit(1)
.get();
return Trip.fromSnapshot(snapshot.docs.first);
}
static void addToLedger(context, documentId, item) async {
await Provider.of(context).db
.collection('userData')
.doc(Provider.of(context).auth.getCurrentUID())
.collection('trips')
.doc(documentId)
.update(item);
}
}
================================================
FILE: lib/views/deposit_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/services/firebase_service.dart';
import 'package:travel_budget/views/navigation_view.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'package:travel_budget/widgets/rounded_button.dart';
class DepositView extends StatefulWidget {
final Trip trip;
DepositView({
@required this.trip,
});
@override
_DepositViewState createState() => _DepositViewState();
}
class _DepositViewState extends State {
String _amount = "0";
String _error;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.indigo,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: SafeArea(
child: Column(
children: [
Spacer(),
FittedBox(
fit: BoxFit.fitWidth,
child: Text(
"\$$_amount",
style: TextStyle(fontSize: 100, fontWeight: FontWeight.bold, color: Colors.white),
),
),
Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Text("${_error ?? ''}", style: TextStyle(color: Colors.white)),
),
Container(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
childAspectRatio: 1.4,
children: setKeyboard(),
),
),
),
Spacer(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_actionBtn("spent"),
_actionBtn("saved"),
],
),
),
Spacer()
],
),
),
);
}
Widget _numberBtn(String number) {
return FlatButton(
child: Text(
"$number",
style: TextStyle(fontSize: 40, color: Colors.white),
),
onPressed: () {
setState(() {
if (_amount == "0") {
_amount = "$number";
} else if (_amount.length == 5) {
_amount = _amount;
HapticFeedback.heavyImpact();
} else {
_amount += "$number";
}
});
},
);
}
Widget _deleteBtn() {
return FlatButton(
child: Text(
"<",
style: TextStyle(fontSize: 40, color: Colors.white),
),
onPressed: () {
setState(() {
if (_amount.length <= 1) {
_amount = "0";
} else {
_amount = _amount.substring(0, _amount.length - 1);
}
});
},
);
}
Widget _actionBtn(String type) {
return Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
child: RoundedButton(
color: Colors.indigoAccent,
child:
Text("${type[0].toUpperCase()}${type.substring(1)}", style: TextStyle(color: Colors.white, fontSize: 20)),
onPressed: () async {
if (_amount == "0") {
setState(() {
_error = "Enter an amount";
});
} else if (type == "spent" && double.parse(_amount) > widget.trip.saved) {
setState(() {
_error = "Yove've only saved \$${widget.trip.saved.floor()}";
});
} else {
FirebaseService.addToLedger(context, widget.trip.documentId, widget.trip.ledgerItem(_amount, type));
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => NavigationView(),
transitionDuration: Duration(seconds: 0),
),
);
}
},
),
),
);
}
setKeyboard() {
List keyboard = [];
// numbers 1-9
List.generate(9, (index) {
keyboard.add(_numberBtn("${index + 1}"));
});
keyboard.add(Text(""));
keyboard.add(_numberBtn("0"));
keyboard.add(_deleteBtn());
return keyboard;
}
}
================================================
FILE: lib/views/detail_trip_view.dart
================================================
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/services/admob_service.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'edit_notes_view.dart';
import 'package:intl/intl.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:travel_budget/widgets/money_text_field.dart';
import 'package:travel_budget/widgets/calculator_widget.dart';
class DetailTripView extends StatefulWidget {
final Trip trip;
DetailTripView({Key key, @required this.trip}) : super(key: key);
@override
_DetailTripViewState createState() => _DetailTripViewState();
}
class _DetailTripViewState extends State {
TextEditingController _budgetController = TextEditingController();
var _budget;
void initState() {
super.initState();
AdMobService.hideHomeBannerAd();
_budgetController.text = widget.trip.budget.toStringAsFixed(0);
_budget = widget.trip.budget.floor();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CustomScrollView(
slivers: [
SliverAppBar(
title: Text('Trip Details'),
backgroundColor: Colors.green,
expandedHeight: 350.0,
flexibleSpace: FlexibleSpaceBar(
background: widget.trip.getLocationImage(),
),
actions: [
IconButton(
icon: Icon(
Icons.settings,
color: Colors.white,
size: 30,
),
padding: const EdgeInsets.only(right: 15),
onPressed: () {
_tripEditModalBottomSheet(context);
},
),
],
),
SliverList(
delegate: SliverChildListDelegate([
tripDetails(),
CalculatorWidget(trip: widget.trip),
totalBudgetCard(),
daysOutCard(),
notesCard(context),
Container(
height: 200,
)
]),
)
],
),
),
);
}
Widget daysOutCard() {
return Card(
color: Colors.amberAccent,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text("${widget.trip.getDaysUntilTrip()}", style: TextStyle(fontSize: 75)),
Text("days until your trip", style: TextStyle(fontSize: 25))
],
),
),
);
}
Widget tripDetails() {
return Card(
child: Column(
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
widget.trip.title,
style: TextStyle(fontSize: 30, color: Colors.green[900]),
),
),
],
),
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
child: Text(
"${DateFormat('MM/dd/yyyy').format(widget.trip.startDate).toString()} - ${DateFormat('MM/dd/yyyy').format(widget.trip.endDate).toString()}"),
),
],
),
],
),
);
}
Widget totalBudgetCard() {
return Card(
color: Colors.blue,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Daily Budget",
style: TextStyle(fontSize: 15, color: Colors.white)),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: AutoSizeText(
"\$$_budget",
style: TextStyle(fontSize: 100),
maxLines: 1,
),
),
],
),
),
Row(
children: [
Expanded(
child: Container(
color: Colors.blue[900],
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 20),
child: Text(
"\$${_budget * widget.trip.getTotalTripDays()} total",
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
)
],
)
],
),
);
}
Widget notesCard(context) {
return Hero(
tag: "TripNotes-${widget.trip.title}",
transitionOnUserGestures: true,
child: Card(
color: Colors.deepPurpleAccent,
child: InkWell(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 10.0, left: 10.0),
child: Row(
children: [
Text("Trip Notes",
style: TextStyle(fontSize: 24, color: Colors.white)),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: setNoteText(),
),
)
],
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EditNotesView(trip: widget.trip)));
},
),
),
);
}
List setNoteText() {
if (widget.trip.notes == null) {
return [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Icon(Icons.add_circle_outline, color: Colors.grey[300]),
),
Text("Click To Add Notes", style: TextStyle(color: Colors.grey[300])),
];
} else {
return [
Text(widget.trip.notes, style: TextStyle(color: Colors.grey[300]))
];
}
}
void _tripEditModalBottomSheet(context) {
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return Container(
height: MediaQuery.of(context).size.height * .60,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
children: [
Text("Edit Trip"),
Spacer(),
IconButton(
icon: Icon(
Icons.cancel,
color: Colors.orange,
size: 25,
),
onPressed: () {
Navigator.of(context).pop();
},
)
],
),
Row(
children: [
Text(
widget.trip.title,
style: TextStyle(fontSize: 30, color: Colors.green[900]),
),
],
),
Row(
children: [
Expanded(
child: MoneyTextField(
controller: _budgetController,
helperText: "Budget",
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text('Submit'),
color: Colors.deepPurple,
textColor: Colors.white,
onPressed: () async {
widget.trip.budget = double.parse(_budgetController.text);
setState(() {
_budget = widget.trip.budget.floor();
});
await updateTrip(context);
Navigator.of(context).pop();
},
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text('Delete'),
color: Colors.red,
textColor: Colors.white,
onPressed: () async {
await deleteTrip(context);
Navigator.of(context).pushNamedAndRemoveUntil('/home', (Route route) => false);
},
)
],
)
],
),
),
);
},
);
}
Future updateTrip(context) async {
var uid = await Provider.of(context).auth.getCurrentUID();
final doc = FirebaseFirestore.instance
.collection('userData')
.doc(uid)
.collection("trips")
.doc(widget.trip.documentId);
return await doc.set(widget.trip.toJson());
}
Future deleteTrip(context) async {
var uid = await Provider.of(context).auth.getCurrentUID();
final doc = FirebaseFirestore.instance
.collection('userData')
.doc(uid)
.collection("trips")
.doc(widget.trip.documentId);
return await doc.delete();
}
}
================================================
FILE: lib/views/edit_notes_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'package:travel_budget/widgets/rounded_button.dart';
class EditNotesView extends StatefulWidget {
final Trip trip;
EditNotesView({Key key, @required this.trip}) : super(key: key);
@override
_EditNotesViewState createState() => _EditNotesViewState();
}
class _EditNotesViewState extends State {
TextEditingController _notesController = new TextEditingController();
final db = FirebaseFirestore.instance;
@override
void initState() {
super.initState();
_notesController.text = widget.trip.notes;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.amberAccent,
child: Hero(
tag: "TripNotes-${widget.trip.title}",
transitionOnUserGestures: true,
child: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
buildHeading(context),
buildNotesText(),
buildSubmitButton(context),
],
),
),
),
),
),
);
}
Widget buildHeading(context) {
return Material(
color: Colors.amberAccent,
child: Padding(
padding: const EdgeInsets.only(left: 20.0, top: 10.0),
child: Row(
children: [
Expanded(
child: Text(
"Trip Notes",
style: TextStyle(fontSize: 24, color: Colors.black),
),
),
FlatButton(
child: Icon(Icons.close, color: Colors.black, size: 30),
onPressed: () {
Navigator.of(context).pop();
},
)
],
),
),
);
}
Widget buildNotesText() {
return Material(
color: Colors.amberAccent,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: TextField(
maxLines: null,
controller: _notesController,
decoration: InputDecoration(
border: InputBorder.none,
),
cursorColor: Colors.black,
autofocus: true,
style: TextStyle(color: Colors.black),
),
),
);
}
Widget buildSubmitButton(context) {
return Material(
color: Colors.amberAccent,
child: RoundedButton(
child: Text("Save", style: TextStyle(color: Colors.white)),
color: Colors.indigo,
onPressed: () async {
widget.trip.notes = _notesController.text;
final uid = Provider.of(context).auth.getCurrentUID();
await db.collection("userData")
.doc(uid)
.collection("trips")
.doc(widget.trip.documentId)
.update({'notes': _notesController.text});
Navigator.of(context).pop();
},
),
);
}
}
================================================
FILE: lib/views/first_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:travel_budget/widgets/custom_dialog.dart';
class FirstView extends StatelessWidget {
final primaryColor = const Color(0xFF75A2EA);
@override
Widget build(BuildContext context) {
final _width = MediaQuery.of(context).size.width;
final _height = MediaQuery.of(context).size.height;
return Scaffold(
body: Container(
width: _width,
height: _height,
color: primaryColor,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
SizedBox(height: _height * 0.10),
Text(
"Welcome",
style: TextStyle(fontSize: 44, color: Colors.white),
),
SizedBox(height: _height * 0.10),
AutoSizeText(
"Let’s start planning your next trip",
maxLines: 2,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 40,
color: Colors.white,
),
),
SizedBox(height: _height * 0.15),
RaisedButton(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)),
child: Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0, left: 30.0, right: 30.0),
child: Text(
"Get Started",
style: TextStyle(
color: primaryColor,
fontSize: 28,
fontWeight: FontWeight.w300,
),
),
),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) => CustomDialog(
title: "Would you like to create a free account?",
description:
"With an account, your data will be securely saved, allowing you to access it from multiple devices.",
primaryButtonText: "Create My Account",
primaryButtonRoute: "/signUp",
secondaryButtonText: "Maybe Later",
secondaryButtonRoute: "/anonymousSignIn",
),
);
},
),
SizedBox(height: _height * 0.05),
FlatButton(
child: Text(
"Sign In",
style: TextStyle(color: Colors.white, fontSize: 25),
),
onPressed: () {
Navigator.of(context).pushReplacementNamed('/signIn');
},
)
],
),
),
),
),
);
}
}
================================================
FILE: lib/views/home_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/views/home_widgets/home_header.dart';
import 'package:travel_budget/views/home_widgets/percent_saved.dart';
import 'package:travel_budget/views/home_widgets/saved_vs_needed.dart';
import 'package:travel_budget/views/home_widgets/travel_type.dart';
import 'package:travel_budget/views/home_widgets/trip_details_card.dart';
import 'home_widgets/current_daily_budget.dart';
import 'home_widgets/days_until_trip.dart';
import 'home_widgets/notes.dart';
class HomeView extends StatefulWidget {
final Trip trip;
HomeView({
@required this.trip,
});
@override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
HomeHeader(widget.trip),
TripDetailsCard(widget.trip),
Padding(
padding: const EdgeInsets.only(left: 15.0, right: 15.0),
child: IntrinsicHeight(
child: Row(
children: [
Expanded(
flex: 12,
child: DaysUntilTrip(widget.trip),
),
Spacer(),
Expanded(
flex: 12,
child: CurrentDailyBudget(widget.trip),
)
],
),
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: SavedVsNeeded(widget.trip),
),
Padding(
padding: const EdgeInsets.only(left: 15.0, right: 15.0),
child: IntrinsicHeight(
child: Row(
children: [
Expanded(
flex: 5,
child: TravelType(widget.trip),
),
Spacer(),
Expanded(
flex: 12,
child: PercentSaved(widget.trip),
)
],
),
),
),
Padding(
padding: const EdgeInsets.all(15.0),
child: Notes(trip: widget.trip),
),
Container(height: 40)
],
),
);
}
}
================================================
FILE: lib/views/home_widgets/current_daily_budget.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
class CurrentDailyBudget extends StatelessWidget {
CurrentDailyBudget(this.trip);
final Trip trip;
@override
Widget build(BuildContext context) {
return Card(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
gradient: LinearGradient(
colors: [Colors.blueAccent, Colors.blue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FittedBox(
fit: BoxFit.fitWidth,
child: Text("\$${trip.getCurrentDailyBudget()}", style: TextStyle(fontSize: 60, color: Colors.white)),
),
FittedBox(
fit: BoxFit.fitWidth,
child: Text("current daily budget", style: TextStyle(color: Colors.white)),
),
],
),
),
),
);
}
}
================================================
FILE: lib/views/home_widgets/days_until_trip.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
class DaysUntilTrip extends StatelessWidget {
DaysUntilTrip(this.trip);
final Trip trip;
@override
Widget build(BuildContext context) {
return Card(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
gradient: LinearGradient(
colors: [Colors.lightBlue, Colors.blueAccent],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FittedBox(
fit: BoxFit.fitWidth,
child: Text("${trip.getDaysUntilTrip()}", style: TextStyle(fontSize: 60, color: Colors.white)),
),
FittedBox(
fit: BoxFit.fitWidth,
child: Text("days until your trip", style: TextStyle(color: Colors.white)),
),
],
),
),
),
);
}
}
================================================
FILE: lib/views/home_widgets/home_header.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
class HomeHeader extends StatelessWidget {
HomeHeader(this.trip);
final Trip trip;
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.25,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.deepPurple, Colors.blueAccent],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
)
),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: FittedBox(
fit: BoxFit.fitWidth,
child: Text("\$${(trip.saved ?? 0.0).floor()}", style: TextStyle(color: Colors.white, fontSize: 65)),
),
),
Text("Total Saved", style: TextStyle(color: Colors.white)),
],
),
),
);
}
}
================================================
FILE: lib/views/home_widgets/notes.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/views/edit_notes_view.dart';
class Notes extends StatelessWidget {
final Trip trip;
Notes({@required this.trip});
@override
Widget build(BuildContext context) {
return Hero(
tag: "TripNotes-${trip.title}",
transitionOnUserGestures: true,
child: Card(
color: Colors.amberAccent,
child: InkWell(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 10.0, top: 8.0),
child: Row(
children: [
Text("Notes", style: TextStyle(fontSize: 24, color: Colors.black)),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: setNoteText(),
),
)
],
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => EditNotesView(trip: trip)),
);
},
),
),
);
}
List setNoteText() {
if (trip.notes == null) {
return [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Icon(Icons.add_circle_outline, color: Colors.grey),
),
Text("Click To Add Notes", style: TextStyle(color: Colors.black)),
];
} else {
return [
Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0),
child: Text(
trip.notes,
style: TextStyle(color: Colors.black),
overflow: TextOverflow.fade,
maxLines: 5,
),
),
)
];
}
}
}
================================================
FILE: lib/views/home_widgets/percent_saved.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/classes/progress_painter.dart';
import 'package:travel_budget/models/Trip.dart';
class PercentSaved extends StatelessWidget {
PercentSaved(this.trip);
final Trip trip;
@override
Widget build(BuildContext context) {
final totalBudget = trip.budget.floor() * trip.getTotalTripDays();
final saved = (trip.saved ?? 0).floor();
final percentComplete = (saved / totalBudget) * 100;
return Card(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
gradient: LinearGradient(
colors: [Colors.indigoAccent, Colors.indigo],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Padding(
padding: const EdgeInsets.all(25.0),
child: Column(
children: [
Container(
height: 200,
width: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Stack(
children: [
Center(
child: Text("${percentComplete.toStringAsFixed(0)}%",
style: TextStyle(color: Colors.white, fontSize: 30))),
CustomPaint(
child: Center(),
painter: ProgressPainter(
circleWidth: 40,
completedPercentage: percentComplete,
defaultCircleColor: Colors.white30,
percentageCompletedCircleColor: Colors.greenAccent,
),
),
],
),
),
),
Container(
child: Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Text("percent saved", style: TextStyle(color: Colors.white)),
),
),
],
),
),
),
);
}
}
================================================
FILE: lib/views/home_widgets/saved_vs_needed.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
class SavedVsNeeded extends StatelessWidget {
SavedVsNeeded(this.trip);
final Trip trip;
@override
Widget build(BuildContext context) {
final saved = (trip.saved ?? 0.0).floor();
final totalBudget = trip.budget.floor() * trip.getTotalTripDays();
final needed = (totalBudget - saved).floor();
return Card(
child: Container (
height: MediaQuery.of(context).size.height * 0.30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
gradient: LinearGradient(
colors: [Colors.blueAccent, Colors.indigo],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 15.0, bottom: 15.0),
child: Text("saved", style: TextStyle(color: Colors.white)),
),
Expanded(
child: Container(
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: (saved > totalBudget) ? 1 : (saved/totalBudget),
child: Container(
width: 50,
color: Colors.white,
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 15.0, bottom: 15.0),
child: Text("\$$saved", style: TextStyle(color: Colors.white)),
),
],
),
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 15.0, bottom: 15.0),
child: Text("needed", style: TextStyle(color: Colors.white)),
),
Expanded(
child: Container(
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: (needed <= 0) ? 0 : (needed/totalBudget),
child: Container(
width: 50,
color: Colors.white,
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 15.0, bottom: 15.0),
child: Text("\$${(needed <= 0) ? 0 : needed}", style: TextStyle(color: Colors.white)),
),
],
)
],
),
),
);
}
}
================================================
FILE: lib/views/home_widgets/travel_type.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
class TravelType extends StatelessWidget {
TravelType(this.trip);
final Trip trip;
Widget getTypeIcon() {
if (trip.types().containsKey(trip.travelType)) {
return trip.types(color: Colors.white)[trip.travelType];
} else {
return Icon(Icons.directions, size: 40, color: Colors.white);
}
}
@override
Widget build(BuildContext context) {
return Card(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
gradient: LinearGradient(
colors: [Colors.lightBlue, Colors.blueAccent],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FittedBox(
fit: BoxFit.fitWidth,
child: Padding(
padding: const EdgeInsets.only(top: 6.0),
child: Text(
"transport",
style: TextStyle(color: Colors.white),
),
),
),
Expanded(
child: getTypeIcon(),
),
Text(trip.travelType, style: TextStyle(color: Colors.white)),
],
),
),
),
);
}
}
================================================
FILE: lib/views/home_widgets/trip_details_card.dart
================================================
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:travel_budget/models/Trip.dart';
class TripDetailsCard extends StatelessWidget {
TripDetailsCard(this.trip);
final Trip trip;
@override
Widget build(BuildContext context) {
return Container(
child: Stack(
overflow: Overflow.visible,
alignment: Alignment.bottomCenter,
children: [
Container(
child: Column(
children: [
SizedBox(
width: double.infinity,
height: 250,
child: trip.getLocationImage(),
),
SizedBox(
height: 50,
width: double.infinity,
)
],
),
),
Positioned(
top: 150,
left: 15,
right: 15,
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
children: [
Expanded(
child: Container(
height: 40,
child: AutoSizeText(
trip.title,
style: TextStyle(fontSize: 30.0),
maxLines: 2,
),
),
)
],
),
Row(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"${DateFormat('MMM dd, yyyy').format(trip.startDate).toString()} - ${DateFormat('MMM dd, yyyy').format(trip.endDate).toString()}"),
),
],
),
Row(
children: [
Text("\$${(trip.budget.floor() * trip.getTotalTripDays()).toString()}",
style: TextStyle(fontSize: 25.0, fontWeight: FontWeight.bold)),
Padding(
padding: const EdgeInsets.only(top: 8.0, left: 4.0),
child: Text("total budget"),
),
],
)
],
),
),
),
)
],
),
);
}
}
================================================
FILE: lib/views/navigation_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/services/firebase_service.dart';
import 'package:travel_budget/views/home_view.dart';
import 'deposit_view.dart';
import 'profile_view.dart';
import 'package:travel_budget/models/Trip.dart';
class NavigationView extends StatefulWidget {
@override
State createState() {
return _NavigationViewState();
}
}
class _NavigationViewState extends State {
Future _nextTrip;
Trip _trip;
int _currentIndex = 0;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_nextTrip = FirebaseService.getNextTrip(context);
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _nextTrip,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if(snapshot.hasData) {
_trip = snapshot.data;
return _buildView();
} else {
// TODO update when user has no trips
return Container(
color: Colors.white,
child: Center(
child: CircularProgressIndicator(),
),
);
}
} else {
return Container(
color: Colors.white,
child: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
_buildView() {
final List _children = [
HomeView(trip: _trip),
DepositView(trip: _trip),
ProfileView(),
];
return Scaffold(
body: _children[_currentIndex],
floatingActionButton: FloatingActionButton(
onPressed: () {
onTabTapped(1);
},
tooltip: "Add Savings",
child: Icon(Icons.attach_money, color: Colors.indigo),
elevation: 4.0,
backgroundColor: Colors.white,
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped,
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text("Home"),
),
BottomNavigationBarItem(
icon: new Icon(Icons.attach_money),
title: new Text("Save"),
),
BottomNavigationBarItem(
icon: new Icon(Icons.account_circle),
title: new Text("Profile"),
),
]
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
================================================
FILE: lib/views/new_trips/budget_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/widgets/divider_with_text_widget.dart';
import 'package:travel_budget/widgets/money_text_field.dart';
import 'summary_view.dart';
enum budgetType { simple, complex }
class NewTripBudgetView extends StatefulWidget {
final Trip trip;
NewTripBudgetView({Key key, @required this.trip}) : super(key: key);
@override
_NewTripBudgetViewState createState() => _NewTripBudgetViewState();
}
class _NewTripBudgetViewState extends State {
var _budgetState = budgetType.simple;
var _switchButtonText = "Build Budget";
var _budgetTotal = 0;
TextEditingController _budgetController = new TextEditingController();
TextEditingController _transportationController = new TextEditingController();
TextEditingController _foodController = new TextEditingController();
TextEditingController _lodgingController = new TextEditingController();
TextEditingController _entertainmentController = new TextEditingController();
@override
void initState() {
super.initState();
_budgetController.addListener(_setBudgetTotal);
_transportationController.addListener(_setTotalBudget);
_foodController.addListener(_setTotalBudget);
_lodgingController.addListener(_setTotalBudget);
_entertainmentController.addListener(_setTotalBudget);
}
_setTotalBudget() {
var total = 0;
total = (_transportationController.text == "") ? 0 : int.parse(_transportationController.text);
total += (_foodController.text == "") ? 0 : int.parse(_foodController.text);
total += (_lodgingController.text== "") ? 0 : int.parse(_lodgingController.text);
total += (_entertainmentController.text == "") ? 0 : int.parse(_entertainmentController.text);
setState(() {
_budgetTotal = total;
});
}
_setBudgetTotal() {
setState(() {
_budgetTotal = (_budgetController.text == "") ? 0 : int.parse(_budgetController.text);
});
}
List setBudgetFields(_budgetController) {
List fields = [];
if (_budgetState == budgetType.simple) {
_switchButtonText = "Build Budget";
fields.add(Padding(
padding: const EdgeInsets.all(12.0),
child: Text("Enter a Trip Budget"),
));
fields.add(MoneyTextField(controller: _budgetController, helperText: "Daily estimated budget"));
} else {
// assumes complex budget
_switchButtonText = "Simple Budget";
fields.add(Padding(
padding: const EdgeInsets.all(12.0),
child: Text("Enter How much you want to spend in each area"),
));
fields.add(MoneyTextField(controller: _transportationController, helperText: "Daily Estimated Transportation Budget"));
fields.add(MoneyTextField(controller: _foodController, helperText: "Daily Estimated Food Budget"));
fields.add(MoneyTextField(controller: _lodgingController, helperText: "Daily Estimated Lodging Budget"));
fields.add(MoneyTextField(controller: _entertainmentController, helperText: "Daily Estimated Entertainment Budget"));
fields.add(Text("Total: \$$_budgetTotal"));
}
fields.add(FlatButton(
child: Text(
"Continue",
style: TextStyle(fontSize: 25, color: Colors.blue),
),
onPressed: () async {
widget.trip.budget = _budgetTotal.toDouble();
widget.trip.budgetTypes = {
'transportation': (_transportationController.text == "") ? 0.0 : double.parse(_transportationController.text),
'food': (_foodController.text == "") ? 0.0 : double.parse(_foodController.text),
'lodging': (_lodgingController.text== "") ? 0.0 : double.parse(_lodgingController.text),
'entertainment': (_entertainmentController.text == "") ? 0.0 : double.parse(_entertainmentController.text),
};
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewTripSummaryView(trip: widget.trip)),
);
},
));
fields.add(DividerWithText(dividerText: "or"));
fields.add(FlatButton(
child: Text(
_switchButtonText,
style: TextStyle(fontSize: 25, color: Colors.blue),
),
onPressed: () {
setState(() {
_budgetState = (_budgetState == budgetType.simple)
? budgetType.complex
: budgetType.simple;
});
},
));
return fields;
}
@override
Widget build(BuildContext context) {
_budgetController.text = (_budgetController.text == "") ? "" : _budgetTotal.toString();
_budgetController.selection = TextSelection.collapsed(offset: _budgetController.text.length);
return Scaffold(
appBar: AppBar(
title: Text('Create Trip - Budget'),
),
body: SingleChildScrollView(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: setBudgetFields(_budgetController),
),
),
),
);
}
}
================================================
FILE: lib/views/new_trips/date_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:date_range_picker/date_range_picker.dart' as DateRagePicker;
import 'package:intl/intl.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'dart:async';
import 'budget_view.dart';
class NewTripDateView extends StatefulWidget {
final Trip trip;
NewTripDateView({Key key, @required this.trip}) : super(key: key);
@override
_NewTripDateViewState createState() => _NewTripDateViewState();
}
class _NewTripDateViewState extends State {
DateTime _startDate = DateTime.now();
DateTime _endDate = DateTime.now().add(Duration(days: 7));
Future displayDateRangePicker(BuildContext context) async {
final List picked = await DateRagePicker.showDatePicker(
context: context,
initialFirstDate: _startDate,
initialLastDate: _endDate,
firstDate: new DateTime(DateTime.now().year - 50),
lastDate: new DateTime(DateTime.now().year + 50));
if (picked != null && picked.length == 2) {
setState(() {
_startDate = picked[0];
_endDate = picked[1];
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CustomScrollView(
slivers: [
SliverAppBar(
title: Text(''),
backgroundColor: Colors.green,
expandedHeight: 350.0,
flexibleSpace: FlexibleSpaceBar(
background: widget.trip.getLocationImage(),
),
),
SliverFixedExtentList(
itemExtent: 200.00,
delegate: SliverChildListDelegate([
buildSelectedDetails(context, widget.trip),
buildButtons(),
]),
)
],
),
),
);
}
Widget buildButtons() {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.60,
child: RaisedButton(
child: Text("Change Date Range"),
color: Colors.deepPurpleAccent,
textColor: Colors.white,
onPressed: () async {
await displayDateRangePicker(context);
},
),
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.60,
child: RaisedButton(
child: Text('Continue'),
color: Colors.amberAccent,
onPressed: () {
widget.trip.startDate = _startDate;
widget.trip.endDate = _endDate;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewTripBudgetView(
trip: widget.trip,
),
),
);
},
),
),
],
);
}
Widget buildingSelectedDates() {
return Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("Start Date"),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
"${DateFormat('MM-dd').format(_startDate).toString()}",
style: TextStyle(fontSize: 35, color: Colors.deepPurple),
),
),
Text(
"${DateFormat('yyyy').format(_startDate).toString()}",
style: TextStyle(color: Colors.deepPurple),
),
],
),
Container(
child: Icon(
Icons.arrow_forward,
color: Colors.deepOrange,
size: 45,
)),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text("End Date"),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
"${DateFormat('MM-dd').format(_endDate).toString()}",
style: TextStyle(fontSize: 35, color: Colors.deepPurple),
),
),
Text(
"${DateFormat('yyyy').format(_endDate).toString()}",
style: TextStyle(color: Colors.deepPurple),
),
],
),
],
),
),
);
}
Widget buildSelectedDetails(BuildContext context, Trip trip) {
return Hero(
tag: "SelectedTrip-${trip.title}",
transitionOnUserGestures: true,
child: Container(
child: Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
),
child: SingleChildScrollView(
child: Card(
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(
top: 16.0, left: 16.0, bottom: 16.0),
child: Column(
children: [
Row(
children: [
Flexible(
child: AutoSizeText(trip.title,
maxLines: 3,
style: TextStyle(fontSize: 25.0)),
),
],
),
buildingSelectedDates(),
],
),
),
),
],
),
),
),
),
),
);
}
}
================================================
FILE: lib/views/new_trips/location_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/models/Place.dart';
import 'package:travel_budget/widgets/divider_with_text_widget.dart';
import 'date_view.dart';
import 'package:travel_budget/credentials.dart';
import 'package:dio/dio.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'dart:async';
import 'package:uuid/uuid.dart';
class NewTripLocationView extends StatefulWidget {
final Trip trip;
NewTripLocationView({Key key, @required this.trip}) : super(key: key);
@override
_NewTripLocationViewState createState() => _NewTripLocationViewState();
}
class _NewTripLocationViewState extends State {
TextEditingController _searchController = new TextEditingController();
// Timer _throttle;
var uuid = new Uuid();
String _sessionToken;
String _heading;
List _placesList;
final List _suggestedList = [
// Place("New York", 320.00),
// Place("Austin", 250.00),
// Place("Boston", 290.00),
// Place("Florence", 300.00),
// Place("Washington D.C.", 190.00),
];
@override
void initState() {
super.initState();
_heading = "Suggestions";
_placesList = _suggestedList;
_searchController.addListener(_onSearchChanged);
}
@override
void dispose() {
_searchController.removeListener(_onSearchChanged);
_searchController.dispose();
super.dispose();
}
_onSearchChanged() {
if(_sessionToken == null) {
setState(() {
_sessionToken = uuid.v4();
});
}
getLocationResults(_searchController.text);
}
void getLocationResults(String input) async {
if (input.isEmpty) {
setState(() {
_heading = "Suggestions";
});
return;
}
String baseURL = 'https://maps.googleapis.com/maps/api/place/autocomplete/json';
String type = '(regions)';
String request = '$baseURL?input=$input&key=$PLACES_API_KEY&type=$type&sessiontoken=$_sessionToken';
Response response = await Dio().get(request);
final predictions = response.data['predictions'];
List _displayResults = [];
for (var i=0; i < predictions.length; i++) {
String name = predictions[i]['description'];
String placeId = predictions[i]['place_id'];
// TODO figure out the budget
double averageBudget = 200.0;
_displayResults.add(Place(name, averageBudget, placeId));
}
setState(() {
_heading = "Results";
_placesList = _displayResults;
});
}
Future getLocationPhotoRef(placeId) async {
String placeImgRequest = 'https://maps.googleapis.com/maps/api/place/details/json?place_id=$placeId&fields=photo,geometry&key=$PLACES_API_KEY&sessiontoken=$_sessionToken';
Response placeDetails = await Dio().get(placeImgRequest);
return placeDetails.data["result"]["photos"][0]["photo_reference"];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Create Trip - Location'),
),
body: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(30.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
),
),
),
Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: new DividerWithText(
dividerText: _heading,
),
),
Expanded(
child: ListView.builder(
itemCount: _placesList.length,
itemBuilder: (BuildContext context, int index) =>
buildPlaceCard(context, index),
),
),
],
),
),
);
}
Widget buildPlaceCard(BuildContext context, int index) {
return Hero(
tag: "SelectedTrip-${_placesList[index].name}",
transitionOnUserGestures: true,
child: Container(
child: Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
),
child: Card(
child: InkWell(
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
children: [
Flexible(
child: AutoSizeText(_placesList[index].name,
maxLines: 3,
style: TextStyle(fontSize: 25.0)),
),
],
),
Row(
children: [
Text(
"Average Budget \$${_placesList[index].averageBudget.toStringAsFixed(2)}"),
],
),
],
),
),
),
],
),
onTap: () async {
String photoReference = await getLocationPhotoRef(_placesList[index].placeId);
widget.trip.title = _placesList[index].name;
widget.trip.photoReference = photoReference;
setState(() {
_sessionToken = null;
});
// TODO maybe pass the trip average budget through here too...
// that would need to be added to the Trip object
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewTripDateView(trip: widget.trip)),
);
},
),
),
),
),
);
}
}
================================================
FILE: lib/views/new_trips/summary_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'package:intl/intl.dart';
import 'package:travel_budget/services/admob_service.dart';
import 'package:firebase_admob/firebase_admob.dart';
class NewTripSummaryView extends StatelessWidget {
final db = FirebaseFirestore.instance;
final Trip trip;
final ams = AdMobService();
NewTripSummaryView({Key key, @required this.trip}) : super(key: key);
@override
Widget build(BuildContext context) {
InterstitialAd newTripAd = ams.getNewTripInterstitial();
newTripAd.load();
final tripTypes = trip.types();
var tripKeys = tripTypes.keys.toList();
return Scaffold(
appBar: AppBar(
title: Text('Trip Summary'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Text("Submit", style: TextStyle(fontSize: 20, color: Colors.green),),
),
Text("${trip.title}"),
Text("${DateFormat('dd/MM/yyyy').format(trip.startDate).toString()} - ${DateFormat('dd/MM/yyyy').format(trip.endDate).toString()}"),
Text("\$${trip.budget.toStringAsFixed(2)}"),
Expanded(
child: GridView.count(
crossAxisCount: 3,
scrollDirection: Axis.vertical,
primary: false,
children: List.generate(tripTypes.length, (index) {
return FlatButton(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
tripTypes[tripKeys[index]],
Text(tripKeys[index]),
],
),
onPressed: () async {
trip.travelType = tripKeys[index];
final uid = await Provider.of(context).auth.getCurrentUID();
await db.collection("userData").doc(uid).collection("trips").add(trip.toJson());
newTripAd.show(
anchorType: AnchorType.bottom,
anchorOffset: 0.0,
horizontalCenterOffset: 0.0,
);
Navigator.of(context).popUntil((route) => route.isFirst);
},
);
}),
),
),
],
)
)
);
}
}
================================================
FILE: lib/views/past_trips_view.dart
================================================
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'package:travel_budget/widgets/trip_card.dart';
class PastTripsView extends StatefulWidget {
@override
_PastTripsViewState createState() => _PastTripsViewState();
}
class _PastTripsViewState extends State {
TextEditingController _searchController = TextEditingController();
Future resultsLoaded;
List _allResults = [];
List _resultsList = [];
@override
void initState() {
super.initState();
_searchController.addListener(_onSearchChanged);
}
@override
void dispose() {
_searchController.removeListener(_onSearchChanged);
_searchController.dispose();
super.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
resultsLoaded = getUsersPastTripsStreamSnapshots();
}
_onSearchChanged() {
searchResultsList();
}
searchResultsList() {
var showResults = [];
if(_searchController.text != "") {
for(var tripSnapshot in _allResults){
var title = Trip.fromSnapshot(tripSnapshot).title.toLowerCase();
if(title.contains(_searchController.text.toLowerCase())) {
showResults.add(tripSnapshot);
}
}
} else {
showResults = List.from(_allResults);
}
setState(() {
_resultsList = showResults;
});
}
getUsersPastTripsStreamSnapshots() async {
final uid = await Provider.of(context).auth.getCurrentUID();
var data = await FirebaseFirestore.instance
.collection('userData')
.doc(uid)
.collection('trips')
.where("endDate", isLessThanOrEqualTo: DateTime.now())
.orderBy('endDate')
.get();
setState(() {
_allResults = data.docs;
});
searchResultsList();
return "complete";
}
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text("Past Trips", style: TextStyle(fontSize: 20)),
Padding(
padding: const EdgeInsets.only(left: 30.0, right: 30.0, bottom: 30.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search)
),
),
),
Expanded(
child: ListView.builder(
itemCount: _resultsList.length,
itemBuilder: (BuildContext context, int index) =>
buildTripCard(context, _resultsList[index]),
)
),
],
),
);
}
}
================================================
FILE: lib/views/profile_view.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'package:intl/intl.dart';
import 'package:travel_budget/models/User.dart';
class ProfileView extends StatefulWidget {
@override
_ProfileViewState createState() => _ProfileViewState();
}
class _ProfileViewState extends State {
User user = User("");
bool _isAdmin = false;
TextEditingController _userCountryController = TextEditingController();
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
FutureBuilder(
future: Provider.of(context).auth.getCurrentUser(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return displayUserInformation(context, snapshot);
} else {
return CircularProgressIndicator();
}
},
)
],
),
),
);
}
Widget displayUserInformation(context, snapshot) {
final authData = snapshot.data;
return Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Provider.of(context).auth.getProfileImage(),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Name: ${authData.displayName ?? 'Anonymous'}",
style: TextStyle(fontSize: 20),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Email: ${authData.email ?? 'Anonymous'}",
style: TextStyle(fontSize: 20),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Created: ${DateFormat('MM/dd/yyyy').format(authData.metadata.creationTime)}",
style: TextStyle(fontSize: 20),
),
),
FutureBuilder(
future: _getProfileData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
_userCountryController.text = user.homeCountry;
_isAdmin = user.admin ?? false;
}
return Container(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Home Country: ${_userCountryController.text}",
style: TextStyle(fontSize: 20),
),
),
adminFeature(),
],
),
);
}
),
showSignOut(context, authData.isAnonymous),
RaisedButton(
child: Text("Edit User"),
onPressed: () {
_userEditBottomSheet(context);
},
)
],
);
}
_getProfileData() async {
final uid = await Provider.of(context).auth.getCurrentUID();
await Provider.of(context)
.db
.collection('userData')
.document(uid)
.get().then((result) {
user.homeCountry = result.data['homeCountry'];
user.admin = result.data['admin'];
});
}
Widget showSignOut(context, bool isAnonymous) {
if (isAnonymous == true) {
return RaisedButton(
child: Text("Sign In To Save Your Data"),
onPressed: () {
Navigator.of(context).pushNamed('/convertUser');
},
);
} else {
return RaisedButton(
child: Text("Sign Out"),
onPressed: () {
try {
Provider.of(context).auth.signOut();
} catch (e) {
print(e);
}
},
);
}
}
Widget adminFeature() {
if(_isAdmin == true) {
return Text("You are an admin");
} else {
return Container();
}
}
void _userEditBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return Container(
height: MediaQuery.of(context).size.height * .60,
child: Padding(
padding: const EdgeInsets.only(left: 15.0, top: 15.0),
child: Column(
children: [
Row(
children: [
Text("Update Profile"),
Spacer(),
IconButton(
icon: Icon(Icons.cancel),
color: Colors.orange,
iconSize: 25,
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 15.0),
child: TextField(
controller: _userCountryController,
decoration: InputDecoration(
helperText: "Home Country",
),
),
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text('Save'),
color: Colors.green,
textColor: Colors.white,
onPressed: () async {
user.homeCountry = _userCountryController.text;
setState(() {
_userCountryController.text = user.homeCountry;
});
final uid =
await Provider.of(context).auth.getCurrentUID();
await Provider.of(context)
.db
.collection('userData')
.document(uid)
.setData(user.toJson());
Navigator.of(context).pop();
},
)
],
),
],
),
),
);
},
);
}
}
================================================
FILE: lib/views/sign_up_view.dart
================================================
import 'package:apple_sign_in/apple_sign_in.dart';
import 'package:flutter/material.dart';
import 'package:travel_budget/services/auth_service.dart';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_auth_buttons/flutter_auth_buttons.dart';
import 'package:international_phone_input/international_phone_input.dart';
// TODO move this to tone location
final primaryColor = const Color(0xFF75A2EA);
enum AuthFormType { signIn, signUp, reset, anonymous, convert, phone }
class SignUpView extends StatefulWidget {
final AuthFormType authFormType;
SignUpView({Key key, @required this.authFormType}) : super(key: key);
@override
_SignUpViewState createState() =>
_SignUpViewState(authFormType: this.authFormType);
}
class _SignUpViewState extends State {
AuthFormType authFormType;
bool _showAppleSignIn = false;
@override
void initState() {
super.initState();
_useAppleSignIn();
}
_useAppleSignIn() async {
final isAvailable = await AppleSignIn.isAvailable();
setState(() {
_showAppleSignIn = isAvailable;
});
}
_SignUpViewState({this.authFormType});
final formKey = GlobalKey();
String _email, _password, _name, _warning, _phone;
void switchFormState(String state) {
formKey.currentState.reset();
if (state == "signUp") {
setState(() {
authFormType = AuthFormType.signUp;
});
} else if (state == 'home') {
Navigator.of(context).pop();
} else {
setState(() {
authFormType = AuthFormType.signIn;
});
}
}
bool validate() {
final form = formKey.currentState;
if (authFormType == AuthFormType.anonymous) {
return true;
}
form.save();
if (form.validate()) {
form.save();
return true;
} else {
return false;
}
}
void submit() async {
if (validate()) {
try {
final auth = Provider.of(context).auth;
switch (authFormType) {
case AuthFormType.signIn:
await auth.signInWithEmailAndPassword(_email, _password);
Navigator.of(context).pushReplacementNamed('/home');
break;
case AuthFormType.signUp:
await auth.createUserWithEmailAndPassword(_email, _password, _name);
Navigator.of(context).pushReplacementNamed('/home');
break;
case AuthFormType.reset:
await auth.sendPasswordResetEmail(_email);
setState(() {
_warning = "A password reset link has been sent to $_email";
authFormType = AuthFormType.signIn;
});
break;
case AuthFormType.anonymous:
await auth.singInAnonymously();
Navigator.of(context).pushReplacementNamed('/home');
break;
case AuthFormType.convert:
await auth.convertUserWithEmail(_email, _password, _name);
Navigator.of(context).pop();
break;
case AuthFormType.phone:
var result = await auth.createUserWithPhone(_phone, context);
if (_phone == "" || result == "error") {
setState(() {
_warning = "Your phone number could not be validated";
});
}
break;
}
} catch (e) {
setState(() {
_warning = e.message;
});
}
}
}
@override
Widget build(BuildContext context) {
final _width = MediaQuery.of(context).size.width;
final _height = MediaQuery.of(context).size.height;
if (authFormType == AuthFormType.anonymous) {
submit();
return Scaffold(
backgroundColor: primaryColor,
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SpinKitDoubleBounce(
color: Colors.white,
),
Text(
"Loading",
style: TextStyle(color: Colors.white),
),
],
),
));
} else {
return Scaffold(
body: SingleChildScrollView(
child: Container(
color: primaryColor,
height: _height,
width: _width,
child: SafeArea(
child: Column(
children: [
SizedBox(height: _height * 0.025),
showAlert(),
SizedBox(height: _height * 0.025),
buildHeaderText(),
SizedBox(height: _height * 0.05),
Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: formKey,
child: Column(
children: buildInputs() + buildButtons(),
),
),
),
],
),
),
),
),
);
}
}
Widget showAlert() {
if (_warning != null) {
return Container(
color: Colors.amberAccent,
width: double.infinity,
padding: EdgeInsets.all(8.0),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Icon(Icons.error_outline),
),
Expanded(
child: AutoSizeText(
_warning,
maxLines: 3,
),
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: IconButton(
icon: Icon(Icons.close),
onPressed: () {
setState(() {
_warning = null;
});
},
),
)
],
),
);
}
return SizedBox(
height: 0,
);
}
AutoSizeText buildHeaderText() {
String _headerText;
if (authFormType == AuthFormType.signIn) {
_headerText = "Sign In";
} else if (authFormType == AuthFormType.reset) {
_headerText = "Reset Password";
} else if (authFormType == AuthFormType.phone) {
_headerText = "Phone Sign In";
} else {
_headerText = "Create New Account";
}
return AutoSizeText(
_headerText,
maxLines: 1,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 35,
color: Colors.white,
),
);
}
void onPhoneNumberChange(
String number, String internationalizedPhoneNumber, String isoCode) {
setState(() {
_phone = internationalizedPhoneNumber;
});
}
List buildInputs() {
List textFields = [];
// if were in the sign up state add name
if ([AuthFormType.signUp, AuthFormType.convert].contains(authFormType)) {
textFields.add(
TextFormField(
validator: NameValidator.validate,
style: TextStyle(fontSize: 22.0),
decoration: buildSignUpInputDecoration("Name"),
onSaved: (value) => _name = value,
),
);
textFields.add(SizedBox(height: 20));
}
// add email & password
if ([
AuthFormType.signUp,
AuthFormType.convert,
AuthFormType.reset,
AuthFormType.signIn
].contains(authFormType)) {
textFields.add(
TextFormField(
validator: EmailValidator.validate,
style: TextStyle(fontSize: 22.0),
decoration: buildSignUpInputDecoration("Email"),
onSaved: (value) => _email = value,
),
);
textFields.add(SizedBox(height: 20));
}
if (authFormType != AuthFormType.reset &&
authFormType != AuthFormType.phone) {
textFields.add(
TextFormField(
validator: PasswordValidator.validate,
style: TextStyle(fontSize: 22.0),
decoration: buildSignUpInputDecoration("Password"),
obscureText: true,
onSaved: (value) => _password = value,
),
);
textFields.add(SizedBox(height: 20));
}
if (authFormType == AuthFormType.phone) {
textFields.add(
InternationalPhoneInput(
decoration: buildSignUpInputDecoration("Enter Phone Number"),
onPhoneNumberChange: onPhoneNumberChange,
initialPhoneNumber: _phone,
initialSelection: 'US',
showCountryCodes: true),
);
textFields.add(SizedBox(height: 20));
}
return textFields;
}
InputDecoration buildSignUpInputDecoration(String hint) {
return InputDecoration(
hintText: hint,
filled: true,
fillColor: Colors.white,
focusColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white, width: 0.0)),
contentPadding:
const EdgeInsets.only(left: 14.0, bottom: 10.0, top: 10.0),
);
}
List buildButtons() {
String _switchButtonText, _newFormState, _submitButtonText;
bool _showForgotPassword = false;
bool _showSocial = true;
if (authFormType == AuthFormType.signIn) {
_switchButtonText = "Create New Account";
_newFormState = "signUp";
_submitButtonText = "Sign In";
_showForgotPassword = true;
} else if (authFormType == AuthFormType.reset) {
_switchButtonText = "Return to Sign In";
_newFormState = "signIn";
_submitButtonText = "Submit";
_showSocial = false;
} else if (authFormType == AuthFormType.convert) {
_switchButtonText = "Cancel";
_newFormState = "home";
_submitButtonText = "Sign Up";
} else if (authFormType == AuthFormType.phone) {
_switchButtonText = "Cancel";
_newFormState = "signIn";
_submitButtonText = "Continue";
_showSocial = false;
} else {
_switchButtonText = "Have an Account? Sign In";
_newFormState = "signIn";
_submitButtonText = "Sign Up";
}
return [
Container(
width: MediaQuery.of(context).size.width * 0.7,
child: RaisedButton(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)),
color: Colors.white,
textColor: primaryColor,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
_submitButtonText,
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.w300),
),
),
onPressed: submit,
),
),
showForgotPassword(_showForgotPassword),
FlatButton(
child: Text(
_switchButtonText,
style: TextStyle(color: Colors.white),
),
onPressed: () {
switchFormState(_newFormState);
},
),
buildSocialIcons(_showSocial),
];
}
Widget showForgotPassword(bool visible) {
return Visibility(
child: FlatButton(
child: Text(
"Forgot Password?",
style: TextStyle(color: Colors.white),
),
onPressed: () {
setState(() {
authFormType = AuthFormType.reset;
});
},
),
visible: visible,
);
}
Widget buildSocialIcons(bool visible) {
final _auth = Provider.of(context).auth;
return Visibility(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Divider(
color: Colors.white,
),
SizedBox(height: 10),
buildAppleSignIn(_auth),
SizedBox(height: 10),
GoogleSignInButton(
onPressed: () async {
try {
if (authFormType == AuthFormType.convert) {
await _auth.convertWithGoogle();
Navigator.of(context).pop();
} else {
await _auth.signInWithGoogle();
Navigator.of(context).pushReplacementNamed('/home');
}
} catch (e) {
setState(() {
_warning = e.message;
});
}
},
),
RaisedButton(
color: Colors.green,
textColor: Colors.white,
child: Row(
children: [
Icon(Icons.phone),
Padding(
padding: const EdgeInsets.only(
left: 14.0, top: 10.0, bottom: 10.0),
child: Text("Sign in with Phone",
style: TextStyle(fontSize: 18)),
)
],
),
onPressed: () {
setState(() {
authFormType = AuthFormType.phone;
});
},
),
],
),
visible: visible,
);
}
Widget buildAppleSignIn(_auth) {
if (authFormType != AuthFormType.convert && _showAppleSignIn == true) {
return AppleSignInButton(
onPressed: () async {
await _auth.signInWithApple();
Navigator.of(context).pushReplacementNamed('/home');
},
style: ButtonStyle.black,
);
} else {
return Container();
}
}
}
================================================
FILE: lib/widgets/calculator_widget.dart
================================================
import 'package:flutter/material.dart';
import 'package:travel_budget/widgets/money_text_field.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/widgets/provider_widget.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class CalculatorWidget extends StatefulWidget {
final Trip trip;
CalculatorWidget({
@required this.trip,
});
@override
_CalculatorWidgetState createState() => _CalculatorWidgetState();
}
class _CalculatorWidgetState extends State {
TextEditingController _moneyController = TextEditingController();
int _saved;
int _needed;
@override
void initState() {
super.initState();
_saved = (widget.trip.saved ?? 0.0).floor();
_needed = (widget.trip.budget.floor() * widget.trip.getTotalTripDays()) - _saved;
}
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
Container(
color: Colors.cyan,
child: Padding(
padding: const EdgeInsets.only(top: 12.0, bottom: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Text("\$$_saved", style: TextStyle(fontSize: 60)),
Text("Saved", style: TextStyle(fontSize: 20)),
],
),
Container(
height: 80,
child: VerticalDivider(
color: Colors.white,
thickness: 5,
),
),
Column(
children: [
Text("\$$_needed", style: TextStyle(fontSize: 60)),
Text("Needed", style: TextStyle(fontSize: 20)),
],
),
],
),
),
),
Container(
color: Colors.orangeAccent,
child: Padding(
padding: const EdgeInsets.only(left: 20.0, right: 40.0),
child: Row(
children: [
Expanded(
child: MoneyTextField(
controller: _moneyController,
helperText: "Save Additional",
),
),
IconButton(
icon: Icon(Icons.add_circle),
color: Colors.green,
iconSize: 50,
onPressed: () async {
setState(() {
_saved = _saved + int.parse(_moneyController.text);
_needed = _needed - int.parse(_moneyController.text);
});
final uid = await Provider.of(context).auth.getCurrentUID();
await FirebaseFirestore.instance.collection('userData')
.doc(uid)
.collection('trips')
.doc(widget.trip.documentId)
.update({'saved': _saved.toDouble()});
},
),
IconButton(
icon: Icon(Icons.remove_circle),
color: Colors.red,
iconSize: 50,
onPressed: () async {
setState(() {
_saved = _saved - int.parse(_moneyController.text);
_needed = _needed + int.parse(_moneyController.text);
});
final uid = await Provider.of(context).auth.getCurrentUID();
await FirebaseFirestore.instance.collection('userData')
.doc(uid)
.collection('trips')
.doc(widget.trip.documentId)
.update({'saved': _saved.toDouble()});
},
)
],
),
),
),
Container(
color: Colors.orangeAccent,
child: Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
generateAddMoneyBtn(25),
generateAddMoneyBtn(50),
generateAddMoneyBtn(100),
],
),
),
)
],
),
);
}
RaisedButton generateAddMoneyBtn(int amount) {
return RaisedButton(
child: Text("\$$amount"),
color: Colors.white,
textColor: Colors.deepOrange,
onPressed: () async {
setState(() {
_saved = _saved + amount;
_needed = _needed - amount;
});
final uid = await Provider.of(context).auth.getCurrentUID();
await FirebaseFirestore.instance.collection('userData')
.doc(uid)
.collection('trips')
.doc(widget.trip.documentId)
.update({'saved': _saved.toDouble()});
},
);
}
}
================================================
FILE: lib/widgets/custom_dialog.dart
================================================
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
class CustomDialog extends StatelessWidget {
final primaryColor = const Color(0xFF75A2EA);
final grayColor = const Color(0xFF939393);
final String title,
description,
primaryButtonText,
primaryButtonRoute,
secondaryButtonText,
secondaryButtonRoute;
CustomDialog(
{@required this.title,
@required this.description,
@required this.primaryButtonText,
@required this.primaryButtonRoute,
this.secondaryButtonText,
this.secondaryButtonRoute});
static const double padding = 20.0;
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(padding),
),
child: Stack(
children: [
Container(
padding: EdgeInsets.all(padding),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(padding),
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 10.0,
offset: const Offset(0.0, 10.0),
),
]),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 24.0),
AutoSizeText(
title,
maxLines: 2,
textAlign: TextAlign.center,
style: TextStyle(
color: primaryColor,
fontSize: 25.0,
),
),
SizedBox(height: 24.0),
AutoSizeText(
description,
maxLines: 4,
textAlign: TextAlign.center,
style: TextStyle(
color: grayColor,
fontSize: 18.0,
),
),
SizedBox(height: 24.0),
RaisedButton(
color: primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0)),
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
child: AutoSizeText(
primaryButtonText,
maxLines: 1,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w200,
color: Colors.white,
),
),
),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context)
.pushReplacementNamed(primaryButtonRoute);
},
),
SizedBox(height: 10.0),
showSecondaryButton(context),
],
),
)
],
),
);
}
showSecondaryButton(BuildContext context) {
if (secondaryButtonRoute != null && secondaryButtonText != null ){
return FlatButton(
child: AutoSizeText(
secondaryButtonText,
maxLines: 1,
style: TextStyle(
fontSize: 18,
color: primaryColor,
fontWeight: FontWeight.w400,
),
),
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed(secondaryButtonRoute);
},
);
} else {
return SizedBox(height: 10.0);
}
}
}
================================================
FILE: lib/widgets/divider_with_text_widget.dart
================================================
import 'package:flutter/material.dart';
class DividerWithText extends StatelessWidget {
final String dividerText;
const DividerWithText({Key key, @required this.dividerText}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(child: Padding(
padding: const EdgeInsets.only(right:8.0),
child: Divider(),
)),
Text(dividerText),
Expanded(child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Divider(),
)),
],
);
}
}
================================================
FILE: lib/widgets/money_text_field.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class MoneyTextField extends StatelessWidget {
final TextEditingController controller;
final String helperText;
const MoneyTextField({Key key, @required this.controller, this.helperText})
: super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(30.0),
child: TextField(
controller: controller,
maxLines: 1,
decoration: InputDecoration(
prefixIcon: Icon(Icons.attach_money),
helperText: helperText,
),
keyboardType: TextInputType.numberWithOptions(decimal: false),
inputFormatters: [
WhitelistingTextInputFormatter.digitsOnly,
],
autofocus: false,
),
);
}
}
================================================
FILE: lib/widgets/provider_widget.dart
================================================
import 'package:travel_budget/services/auth_service.dart';
import 'package:flutter/material.dart';
class Provider extends InheritedWidget {
final AuthService auth;
final db;
final colors;
Provider({Key key, Widget child, this.auth, this.db, this.colors}) : super(key: key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
static Provider of(BuildContext context) =>
(context.dependOnInheritedWidgetOfExactType());
}
================================================
FILE: lib/widgets/rounded_button.dart
================================================
import 'package:flutter/material.dart';
class RoundedButton extends RaisedButton {
final VoidCallback onPressed;
final Widget child;
final Color color;
const RoundedButton({@required this.onPressed, this.child, this.color}) : super(onPressed: onPressed, child: child);
@override
Widget build(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(
buttonTheme: Theme.of(context).buttonTheme.copyWith(
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0)),
buttonColor: color,
)
),
child: Builder(builder: super.build),
);
}
}
================================================
FILE: lib/widgets/trip_card.dart
================================================
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:travel_budget/models/Trip.dart';
import 'package:travel_budget/services/admob_service.dart';
import 'package:travel_budget/views/detail_trip_view.dart';
Widget buildTripCard(BuildContext context, DocumentSnapshot document, [bool loadBannerAd]) {
final trip = Trip.fromSnapshot(document);
final tripType = trip.types();
return new Container(
child: Card(
child: InkWell(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 4.0),
child: Row(children: [
Text(
trip.title,
style: GoogleFonts.seymourOne(fontSize: 20.0),
),
Spacer(),
]),
),
Padding(
padding: const EdgeInsets.only(top: 4.0, bottom: 80.0),
child: Row(children: [
Text(
"${DateFormat('MM/dd/yyyy').format(trip.startDate).toString()} - ${DateFormat('MM/dd/yyyy').format(trip.endDate).toString()}"),
Spacer(),
]),
),
Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Row(
children: [
Text(
"\$${(trip.budget == null) ? "n/a" : trip.budget.toStringAsFixed(2)}",
style: new TextStyle(fontSize: 35.0),
),
Spacer(),
(tripType.containsKey(trip.travelType)) ? tripType[trip.travelType] : tripType["other"],
],
),
)
],
),
),
onTap: () {
if (loadBannerAd == true) {
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailTripView(trip: trip))).then((value) {
AdMobService.showHomeBannerAd();
});
} else {
Navigator.push(context, MaterialPageRoute(builder: (context) => DetailTripView(trip: trip)));
}
},
),
),
);
}
================================================
FILE: pubspec.yaml
================================================
name: travel_budget
description: A new Flutter application.
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
firebase_core: ^0.5.3
cloud_firestore: ^0.14.0
firebase_auth: ^0.18.0
google_sign_in: ^4.0.4
apple_sign_in: ^0.1.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
intl: 0.16.1
date_range_picker: ^1.0.5
auto_size_text: ^2.0.2
flutter_spinkit: ^3.1.0
flutter_auth_buttons: ^0.5.0
dio: ^3.0.10
uuid: 2.0.1
device_info: ^0.4.1+4
google_fonts: ^0.3.2
firebase_admob: ^0.10.0-dev.1
flare_flutter: ^2.0.3
international_phone_input: ^1.0.4
flutter_launcher_icons: "^0.7.3"
firebase_crashlytics: "^0.2.4"
# flutter pub run flutter_launcher_icons:main
flutter_icons:
image_path: "assets/icons/main_logo.png"
adaptive_icon_foreground: "assets/icons/foreground_logo.png"
adaptive_icon_background: "#57AEAF"
android: true
ios: true
dev_dependencies:
flutter_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- assets/
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
================================================
FILE: res/values/strings_en.arb
================================================
================================================
FILE: test/widget_test.dart
================================================
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:travel_budget/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}