├── res
└── values
│ └── strings_en.arb
├── android
├── settings_aar.gradle
├── key.properties
├── gradle.properties
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── drawable
│ │ │ │ │ ├── launch_screen.png
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── drawable-hdpi
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-mdpi
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ └── mipmap-anydpi-v26
│ │ │ │ │ └── ic_launcher.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── a1manstartup
│ │ │ │ │ └── travel_budget
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── build.gradle
├── ios
├── Flutter
│ ├── .last_build_id
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ ├── Flutter.podspec
│ ├── flutter_export_environment.sh
│ └── AppFrameworkInfo.plist
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-50x50@1x.png
│ │ │ ├── Icon-App-50x50@2x.png
│ │ │ ├── Icon-App-57x57@1x.png
│ │ │ ├── Icon-App-57x57@2x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-72x72@1x.png
│ │ │ ├── Icon-App-72x72@2x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ │ └── LaunchImage.imageset
│ │ │ ├── launch_screen.png
│ │ │ ├── launch_screen@2x.png
│ │ │ ├── launch_screen@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ ├── Runner.entitlements
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Podfile
├── assets
├── sun_clouds.flr
└── icons
│ ├── main_logo.png
│ └── foreground_logo.png
├── lib
├── models
│ ├── Place.dart
│ ├── User.dart
│ └── Trip.dart
├── services
│ ├── custom_colors.dart
│ ├── firebase_service.dart
│ ├── admob_service.dart
│ └── auth_service.dart
├── widgets
│ ├── provider_widget.dart
│ ├── divider_with_text_widget.dart
│ ├── rounded_button.dart
│ ├── money_text_field.dart
│ ├── trip_card.dart
│ ├── custom_dialog.dart
│ └── calculator_widget.dart
├── views
│ ├── home_widgets
│ │ ├── home_header.dart
│ │ ├── days_until_trip.dart
│ │ ├── current_daily_budget.dart
│ │ ├── travel_type.dart
│ │ ├── notes.dart
│ │ ├── percent_saved.dart
│ │ ├── trip_details_card.dart
│ │ └── saved_vs_needed.dart
│ ├── home_view.dart
│ ├── new_trips
│ │ ├── summary_view.dart
│ │ ├── budget_view.dart
│ │ ├── location_view.dart
│ │ └── date_view.dart
│ ├── navigation_view.dart
│ ├── past_trips_view.dart
│ ├── first_view.dart
│ ├── edit_notes_view.dart
│ ├── deposit_view.dart
│ ├── profile_view.dart
│ ├── detail_trip_view.dart
│ └── sign_up_view.dart
├── classes
│ └── progress_painter.dart
├── main.dart
└── generated
│ └── i18n.dart
├── .metadata
├── .github
└── FUNDING.yml
├── LICENSE
├── test
└── widget_test.dart
├── README.md
├── .gitignore
├── pubspec.yaml
├── .flutter-plugins-dependencies
└── pubspec.lock
/res/values/strings_en.arb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/android/settings_aar.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/ios/Flutter/.last_build_id:
--------------------------------------------------------------------------------
1 | ff43a8078c18a80d1d983aec4895d6ad
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/assets/sun_clouds.flr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/assets/sun_clouds.flr
--------------------------------------------------------------------------------
/assets/icons/main_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/assets/icons/main_logo.png
--------------------------------------------------------------------------------
/assets/icons/foreground_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/assets/icons/foreground_logo.png
--------------------------------------------------------------------------------
/android/key.properties:
--------------------------------------------------------------------------------
1 | storePassword=password123
2 | keyPassword=password123
3 | keyAlias=key
4 | storeFile=/Users/davefaliskie/key.jks
5 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.enableR8=true
5 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/drawable/launch_screen.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #57AEAF
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/launch_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/launch_screen.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/launch_screen@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/launch_screen@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/launch_screen@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/launch_screen@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davefaliskie/travel_treasury/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/lib/models/Place.dart:
--------------------------------------------------------------------------------
1 | class Place {
2 | String name;
3 | double averageBudget;
4 | String placeId;
5 |
6 | Place(
7 | this.name,
8 | this.averageBudget,
9 | this.placeId,
10 | );
11 | }
--------------------------------------------------------------------------------
/lib/models/User.dart:
--------------------------------------------------------------------------------
1 | class User {
2 | String homeCountry;
3 | bool admin;
4 |
5 | User(this.homeCountry);
6 |
7 | Map toJson() => {
8 | 'homeCountry': homeCountry,
9 | 'admin': admin,
10 | };
11 | }
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/Runner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.applesignin
6 |
7 | Default
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/a1manstartup/travel_budget/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.a1manstartup.travel_budget
2 |
3 | import android.os.Bundle
4 |
5 | import io.flutter.app.FlutterActivity
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity: FlutterActivity() {
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | GeneratedPluginRegistrant.registerWith(this)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | -
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/services/custom_colors.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CustomColors {
4 | Brightness brightness;
5 | Color text1, primary;
6 |
7 | CustomColors(Brightness brightness) {
8 | this.brightness = brightness;
9 |
10 | if(brightness == Brightness.dark) {
11 | this.text1 = Color(0xff252223);
12 | this.primary = Color(0xff3c7778);
13 |
14 | } else {
15 | this.text1 = Colors.white;
16 | this.primary = Color(0xff57AEAF);
17 | }
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "launch_screen.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "launch_screen@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "launch_screen@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/lib/widgets/provider_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:travel_budget/services/auth_service.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class Provider extends InheritedWidget {
5 | final AuthService auth;
6 | final db;
7 | final colors;
8 |
9 | Provider({Key key, Widget child, this.auth, this.db, this.colors}) : super(key: key, child: child);
10 |
11 | @override
12 | bool updateShouldNotify(InheritedWidget oldWidget) {
13 | return true;
14 | }
15 |
16 | static Provider of(BuildContext context) =>
17 | (context.dependOnInheritedWidgetOfExactType());
18 | }
--------------------------------------------------------------------------------
/lib/widgets/divider_with_text_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class DividerWithText extends StatelessWidget {
4 | final String dividerText;
5 | const DividerWithText({Key key, @required this.dividerText}) : super(key: key);
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Row(
10 | children: [
11 | Expanded(child: Padding(
12 | padding: const EdgeInsets.only(right:8.0),
13 | child: Divider(),
14 | )),
15 | Text(dividerText),
16 | Expanded(child: Padding(
17 | padding: const EdgeInsets.only(left: 8.0),
18 | child: Divider(),
19 | )),
20 | ],
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/widgets/rounded_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class RoundedButton extends RaisedButton {
4 | final VoidCallback onPressed;
5 | final Widget child;
6 | final Color color;
7 |
8 | const RoundedButton({@required this.onPressed, this.child, this.color}) : super(onPressed: onPressed, child: child);
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Theme(
13 | data: Theme.of(context).copyWith(
14 | buttonTheme: Theme.of(context).buttonTheme.copyWith(
15 | shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0)),
16 | buttonColor: color,
17 | )
18 | ),
19 | child: Builder(builder: super.build),
20 | );
21 | }
22 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 | custom: ['https://1manstartup.com/donate']
14 |
--------------------------------------------------------------------------------
/ios/Flutter/Flutter.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # NOTE: This podspec is NOT to be published. It is only used as a local source!
3 | #
4 |
5 | Pod::Spec.new do |s|
6 | s.name = 'Flutter'
7 | s.version = '1.0.0'
8 | s.summary = 'High-performance, high-fidelity mobile apps.'
9 | s.description = <<-DESC
10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
11 | DESC
12 | s.homepage = 'https://flutter.io'
13 | s.license = { :type => 'MIT' }
14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
16 | s.ios.deployment_target = '8.0'
17 | s.vendored_frameworks = 'Flutter.framework'
18 | end
19 |
--------------------------------------------------------------------------------
/ios/Flutter/flutter_export_environment.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # This is a generated file; do not edit or check into version control.
3 | export "FLUTTER_ROOT=/Users/davefaliskie/flutter"
4 | export "FLUTTER_APPLICATION_PATH=/Users/davefaliskie/Code/travel_budget"
5 | export "FLUTTER_TARGET=/Users/davefaliskie/Code/travel_budget/lib/main.dart"
6 | export "FLUTTER_BUILD_DIR=build"
7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios"
8 | export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
9 | export "FLUTTER_FRAMEWORK_DIR=/Users/davefaliskie/flutter/bin/cache/artifacts/engine/ios"
10 | export "FLUTTER_BUILD_NAME=1.0.0"
11 | export "FLUTTER_BUILD_NUMBER=1"
12 | export "DART_DEFINES=flutter.inspector.structuredErrors%3Dtrue"
13 | export "DART_OBFUSCATION=false"
14 | export "TRACK_WIDGET_CREATION=true"
15 | export "TREE_SHAKE_ICONS=false"
16 | export "PACKAGE_CONFIG=.packages"
17 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.21'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.3.1'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | classpath 'com.google.gms:google-services:4.3.3'
12 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0'
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | google()
19 | jcenter()
20 | }
21 | }
22 |
23 | rootProject.buildDir = '../build'
24 | subprojects {
25 | project.buildDir = "${rootProject.buildDir}/${project.name}"
26 | }
27 | subprojects {
28 | project.evaluationDependsOn(':app')
29 | }
30 |
31 | task clean(type: Delete) {
32 | delete rootProject.buildDir
33 | }
34 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/services/firebase_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 | import 'package:travel_budget/widgets/provider_widget.dart';
4 |
5 | class FirebaseService {
6 |
7 | static Future getNextTrip(context) async {
8 | final uid = Provider.of(context).auth.getCurrentUID();
9 | var snapshot = await FirebaseFirestore.instance
10 | .collection('userData')
11 | .doc(uid)
12 | .collection('trips')
13 | .orderBy('startDate')
14 | .limit(1)
15 | .get();
16 | return Trip.fromSnapshot(snapshot.docs.first);
17 | }
18 |
19 | static void addToLedger(context, documentId, item) async {
20 | await Provider.of(context).db
21 | .collection('userData')
22 | .doc(Provider.of(context).auth.getCurrentUID())
23 | .collection('trips')
24 | .doc(documentId)
25 | .update(item);
26 | }
27 | }
--------------------------------------------------------------------------------
/lib/widgets/money_text_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 |
4 | class MoneyTextField extends StatelessWidget {
5 | final TextEditingController controller;
6 | final String helperText;
7 |
8 | const MoneyTextField({Key key, @required this.controller, this.helperText})
9 | : super(key: key);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Padding(
14 | padding: const EdgeInsets.all(30.0),
15 | child: TextField(
16 | controller: controller,
17 | maxLines: 1,
18 | decoration: InputDecoration(
19 | prefixIcon: Icon(Icons.attach_money),
20 | helperText: helperText,
21 | ),
22 | keyboardType: TextInputType.numberWithOptions(decimal: false),
23 | inputFormatters: [
24 | WhitelistingTextInputFormatter.digitsOnly,
25 | ],
26 | autofocus: false,
27 | ),
28 | );
29 | }
30 | }
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Dave Faliskie
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:travel_budget/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/lib/views/home_widgets/home_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 |
4 | class HomeHeader extends StatelessWidget {
5 | HomeHeader(this.trip);
6 | final Trip trip;
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Container(
11 | width: MediaQuery.of(context).size.width,
12 | height: MediaQuery.of(context).size.height * 0.25,
13 | decoration: BoxDecoration(
14 | gradient: LinearGradient(
15 | colors: [Colors.deepPurple, Colors.blueAccent],
16 | begin: Alignment.topCenter,
17 | end: Alignment.bottomCenter,
18 | )
19 | ),
20 |
21 | child: SafeArea(
22 | child: Column(
23 | mainAxisAlignment: MainAxisAlignment.center,
24 | children: [
25 | Padding(
26 | padding: const EdgeInsets.all(8.0),
27 | child: FittedBox(
28 | fit: BoxFit.fitWidth,
29 | child: Text("\$${(trip.saved ?? 0.0).floor()}", style: TextStyle(color: Colors.white, fontSize: 65)),
30 | ),
31 | ),
32 | Text("Total Saved", style: TextStyle(color: Colors.white)),
33 | ],
34 | ),
35 | ),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Travel Treasury
2 | [Download The Live App](https://traveltreasury.app)
3 |
4 | 
5 |
6 | This repository contains all the code written throughout the 1ManStartup YouTube tutorials for building a travel budget app using Flutter
7 |
8 | [View the channel on YouTube](https://www.youtube.com/channel/UC8xcnxN4CyXdPCeUN1eURPg)
9 |
10 | ## How To Use This Resource
11 |
12 | Each episode where code is created or modified will have an associated branch
13 | in this repo. The code in each episode's branch will contain the completed code from that episode
14 | and the branch will remain in that state.
15 |
16 | The master branch will contain the most recent version of code, and be considered the "production" version.
17 | This means the master branch will always be the most up to date.
18 |
19 |
20 | Any questions should be asked in the comments of the relevant video on YouTube.
21 |
22 |
23 | ## Setup Firebase Database
24 |
25 | 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)
26 |
--------------------------------------------------------------------------------
/lib/views/home_widgets/days_until_trip.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 |
4 | class DaysUntilTrip extends StatelessWidget {
5 | DaysUntilTrip(this.trip);
6 | final Trip trip;
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Card(
11 | child: Container(
12 | decoration: BoxDecoration(
13 | borderRadius: BorderRadius.circular(4.0),
14 | gradient: LinearGradient(
15 | colors: [Colors.lightBlue, Colors.blueAccent],
16 | begin: Alignment.topLeft,
17 | end: Alignment.bottomRight,
18 | ),
19 | ),
20 | child: Padding(
21 | padding: const EdgeInsets.all(20.0),
22 | child: Column(
23 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
24 | children: [
25 | FittedBox(
26 | fit: BoxFit.fitWidth,
27 | child: Text("${trip.getDaysUntilTrip()}", style: TextStyle(fontSize: 60, color: Colors.white)),
28 | ),
29 | FittedBox(
30 | fit: BoxFit.fitWidth,
31 | child: Text("days until your trip", style: TextStyle(color: Colors.white)),
32 | ),
33 | ],
34 | ),
35 | ),
36 | ),
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/views/home_widgets/current_daily_budget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 |
4 | class CurrentDailyBudget extends StatelessWidget {
5 | CurrentDailyBudget(this.trip);
6 | final Trip trip;
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Card(
11 | child: Container(
12 | decoration: BoxDecoration(
13 | borderRadius: BorderRadius.circular(4.0),
14 | gradient: LinearGradient(
15 | colors: [Colors.blueAccent, Colors.blue],
16 | begin: Alignment.topLeft,
17 | end: Alignment.bottomRight,
18 | ),
19 | ),
20 | child: Padding(
21 | padding: const EdgeInsets.all(20.0),
22 | child: Column(
23 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
24 | children: [
25 | FittedBox(
26 | fit: BoxFit.fitWidth,
27 | child: Text("\$${trip.getCurrentDailyBudget()}", style: TextStyle(fontSize: 60, color: Colors.white)),
28 | ),
29 | FittedBox(
30 | fit: BoxFit.fitWidth,
31 | child: Text("current daily budget", style: TextStyle(color: Colors.white)),
32 | ),
33 | ],
34 | ),
35 | ),
36 | ),
37 | );
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/lib/classes/progress_painter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:math';
3 |
4 | class ProgressPainter extends CustomPainter {
5 | Color defaultCircleColor;
6 | Color percentageCompletedCircleColor;
7 | double completedPercentage;
8 | double circleWidth;
9 |
10 | ProgressPainter(
11 | {this.defaultCircleColor,
12 | this.percentageCompletedCircleColor,
13 | this.completedPercentage,
14 | this.circleWidth});
15 |
16 | getPaint(Color color) {
17 | return Paint()
18 | ..color = color
19 | ..strokeCap = StrokeCap.round
20 | ..style = PaintingStyle.stroke
21 | ..strokeWidth = circleWidth;
22 | }
23 |
24 | @override
25 | void paint(Canvas canvas, Size size) {
26 | Paint defaultCirclePaint = getPaint(defaultCircleColor);
27 | Paint progressCirclePaint = getPaint(percentageCompletedCircleColor);
28 |
29 | Offset center = Offset(size.width / 2, size.height / 2);
30 | double radius = min(size.width / 2, size.height / 2);
31 | canvas.drawCircle(center, radius, defaultCirclePaint);
32 |
33 | double arcAngle = 2 * pi * (completedPercentage / 100);
34 | canvas.drawArc(Rect.fromCircle(center: center, radius: radius), -pi / 2,
35 | arcAngle, false, progressCirclePaint);
36 | }
37 |
38 | @override
39 | bool shouldRepaint(CustomPainter painter) {
40 | return true;
41 | }
42 | }
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # Visual Studio Code related
19 | .vscode/
20 |
21 | # Flutter/Dart/Pub related
22 | **/doc/api/
23 | .dart_tool/
24 | .flutter-plugins
25 | .flutter-plugins-dependencies
26 | .packages
27 | .pub-cache/
28 | .pub/
29 | /build/
30 |
31 | # Android related
32 | **/android/**/gradle-wrapper.jar
33 | **/android/.gradle
34 | **/android/captures/
35 | **/android/gradlew
36 | **/android/gradlew.bat
37 | **/android/local.properties
38 | **/android/**/GeneratedPluginRegistrant.java
39 | **/android/key.properties
40 |
41 | # iOS/XCode related
42 | **/ios/**/*.mode1v3
43 | **/ios/**/*.mode2v3
44 | **/ios/**/*.moved-aside
45 | **/ios/**/*.pbxuser
46 | **/ios/**/*.perspectivev3
47 | **/ios/**/*sync/
48 | **/ios/**/.sconsign.dblite
49 | **/ios/**/.tags*
50 | **/ios/**/.vagrant/
51 | **/ios/**/DerivedData/
52 | **/ios/**/Icon?
53 | **/ios/**/Pods/
54 | **/ios/**/.symlinks/
55 | **/ios/**/profile
56 | **/ios/**/xcuserdata
57 | **/ios/.generated/
58 | **/ios/Flutter/App.framework
59 | **/ios/Flutter/Flutter.framework
60 | **/ios/Flutter/Generated.xcconfig
61 | **/ios/Flutter/app.flx
62 | **/ios/Flutter/app.zip
63 | **/ios/Flutter/flutter_assets/
64 | **/ios/ServiceDefinitions.json
65 | **/ios/Runner/GeneratedPluginRegistrant.*
66 |
67 | # Exceptions to above rules.
68 | !**/ios/**/default.mode1v3
69 | !**/ios/**/default.mode2v3
70 | !**/ios/**/default.pbxuser
71 | !**/ios/**/default.perspectivev3
72 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
73 |
74 |
75 | *google-services.json
76 | *GoogleService-Info.plist
77 | *lib/credentials.dart
--------------------------------------------------------------------------------
/lib/views/home_widgets/travel_type.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 |
4 | class TravelType extends StatelessWidget {
5 | TravelType(this.trip);
6 | final Trip trip;
7 |
8 | Widget getTypeIcon() {
9 | if (trip.types().containsKey(trip.travelType)) {
10 | return trip.types(color: Colors.white)[trip.travelType];
11 | } else {
12 | return Icon(Icons.directions, size: 40, color: Colors.white);
13 | }
14 | }
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Card(
19 | child: Container(
20 | decoration: BoxDecoration(
21 | borderRadius: BorderRadius.circular(4.0),
22 | gradient: LinearGradient(
23 | colors: [Colors.lightBlue, Colors.blueAccent],
24 | begin: Alignment.topLeft,
25 | end: Alignment.bottomRight,
26 | ),
27 | ),
28 | child: Padding(
29 | padding: const EdgeInsets.all(20.0),
30 | child: Column(
31 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
32 | children: [
33 | FittedBox(
34 | fit: BoxFit.fitWidth,
35 | child: Padding(
36 | padding: const EdgeInsets.only(top: 6.0),
37 | child: Text(
38 | "transport",
39 | style: TextStyle(color: Colors.white),
40 | ),
41 | ),
42 | ),
43 | Expanded(
44 | child: getTypeIcon(),
45 | ),
46 | Text(trip.travelType, style: TextStyle(color: Colors.white)),
47 | ],
48 | ),
49 | ),
50 | ),
51 |
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/services/admob_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:firebase_admob/firebase_admob.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class AdMobService {
6 |
7 | String getAdMobAppId() {
8 | if (Platform.isIOS) {
9 | return 'ca-app-pub-2334510780816542~6726672523';
10 | } else if (Platform.isAndroid) {
11 | return 'ca-app-pub-2334510780816542~7385148076';
12 | }
13 | return null;
14 | }
15 |
16 | static String _getBannerAdId() {
17 | if (Platform.isIOS) {
18 | // return 'ca-app-pub-2334510780816542/6833456062';
19 | return 'ca-app-pub-3940256099942544/2934735716';
20 | } else if (Platform.isAndroid) {
21 | // return 'ca-app-pub-2334510780816542/2993163849';
22 | return "ca-app-pub-3940256099942544/6300978111";
23 | }
24 | return null;
25 | }
26 |
27 | String getInterstitialAdId() {
28 | if (Platform.isIOS) {
29 | // return '';
30 | return 'ca-app-pub-3940256099942544/4411468910';
31 | } else if (Platform.isAndroid) {
32 | // return '';
33 | return "ca-app-pub-3940256099942544/1033173712";
34 | }
35 | return null;
36 | }
37 |
38 |
39 | InterstitialAd getNewTripInterstitial() {
40 | return InterstitialAd(
41 | adUnitId: getInterstitialAdId(),
42 | listener: (MobileAdEvent event) {
43 | print("InterstitialAd event is $event");
44 | },
45 | );
46 | }
47 |
48 | static BannerAd _homeBannerAd;
49 |
50 | static BannerAd _getHomePageBannerAd() {
51 | return BannerAd(
52 | adUnitId: _getBannerAdId(),
53 | size: AdSize.smartBanner
54 | );
55 | }
56 |
57 | static void showHomeBannerAd() {
58 | if ( _homeBannerAd == null ) _homeBannerAd = _getHomePageBannerAd();
59 | _homeBannerAd
60 | ..load()
61 | ..show(anchorType: AnchorType.bottom, anchorOffset: kBottomNavigationBarHeight);
62 | }
63 |
64 | static void hideHomeBannerAd() async {
65 | await _homeBannerAd.dispose();
66 | _homeBannerAd = null;
67 | }
68 |
69 |
70 | }
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
13 |
20 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lib/views/home_widgets/notes.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 | import 'package:travel_budget/views/edit_notes_view.dart';
4 |
5 | class Notes extends StatelessWidget {
6 | final Trip trip;
7 |
8 | Notes({@required this.trip});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Hero(
13 | tag: "TripNotes-${trip.title}",
14 | transitionOnUserGestures: true,
15 | child: Card(
16 | color: Colors.amberAccent,
17 | child: InkWell(
18 | child: Column(
19 | children: [
20 | Padding(
21 | padding: const EdgeInsets.only(left: 10.0, top: 8.0),
22 | child: Row(
23 | children: [
24 | Text("Notes", style: TextStyle(fontSize: 24, color: Colors.black)),
25 | ],
26 | ),
27 | ),
28 | Padding(
29 | padding: const EdgeInsets.all(8.0),
30 | child: Row(
31 | children: setNoteText(),
32 | ),
33 | )
34 | ],
35 | ),
36 | onTap: () {
37 | Navigator.push(
38 | context,
39 | MaterialPageRoute(builder: (context) => EditNotesView(trip: trip)),
40 | );
41 | },
42 | ),
43 | ),
44 | );
45 | }
46 |
47 | List setNoteText() {
48 | if (trip.notes == null) {
49 | return [
50 | Padding(
51 | padding: const EdgeInsets.only(right: 8.0),
52 | child: Icon(Icons.add_circle_outline, color: Colors.grey),
53 | ),
54 | Text("Click To Add Notes", style: TextStyle(color: Colors.black)),
55 | ];
56 | } else {
57 | return [
58 | Flexible(
59 | child: Padding(
60 | padding: const EdgeInsets.only(left: 8.0, right: 8.0),
61 | child: Text(
62 | trip.notes,
63 | style: TextStyle(color: Colors.black),
64 | overflow: TextOverflow.fade,
65 | maxLines: 5,
66 | ),
67 | ),
68 | )
69 | ];
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | Travel Treasury
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleTypeRole
25 | Editor
26 | CFBundleURLSchemes
27 |
28 | com.googleusercontent.apps.635382246089-feadgcif5jb704jtq2nir0d6cnh8fu02
29 |
30 |
31 |
32 | CFBundleTypeRole
33 | Editor
34 | CFBundleURLSchemes
35 |
36 | com.googleusercontent.apps.635382246089-egv2iuv19s1e0gjiq7rqvngu4c7e7su0
37 |
38 |
39 |
40 | CFBundleVersion
41 | $(FLUTTER_BUILD_NUMBER)
42 | GADApplicationIdentifier
43 | ca-app-pub-2334510780816542~6726672523
44 | LSApplicationCategoryType
45 |
46 | LSRequiresIPhoneOS
47 |
48 | UIBackgroundModes
49 |
50 | fetch
51 | remote-notification
52 |
53 | UILaunchStoryboardName
54 | LaunchScreen
55 | UIMainStoryboardFile
56 | Main
57 | UISupportedInterfaceOrientations
58 |
59 | UIInterfaceOrientationPortrait
60 |
61 | UISupportedInterfaceOrientations~ipad
62 |
63 | UIInterfaceOrientationPortrait
64 | UIInterfaceOrientationPortraitUpsideDown
65 | UIInterfaceOrientationLandscapeLeft
66 | UIInterfaceOrientationLandscapeRight
67 |
68 | UIViewControllerBasedStatusBarAppearance
69 |
70 | io.flutter.embedded_views_preview
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/lib/views/home_widgets/percent_saved.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/classes/progress_painter.dart';
3 | import 'package:travel_budget/models/Trip.dart';
4 |
5 | class PercentSaved extends StatelessWidget {
6 | PercentSaved(this.trip);
7 | final Trip trip;
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | final totalBudget = trip.budget.floor() * trip.getTotalTripDays();
12 | final saved = (trip.saved ?? 0).floor();
13 | final percentComplete = (saved / totalBudget) * 100;
14 |
15 | return Card(
16 | child: Container(
17 | decoration: BoxDecoration(
18 | borderRadius: BorderRadius.circular(4.0),
19 | gradient: LinearGradient(
20 | colors: [Colors.indigoAccent, Colors.indigo],
21 | begin: Alignment.topLeft,
22 | end: Alignment.bottomRight,
23 | ),
24 | ),
25 | child: Padding(
26 | padding: const EdgeInsets.all(25.0),
27 | child: Column(
28 | children: [
29 | Container(
30 | height: 200,
31 | width: 200,
32 | decoration: BoxDecoration(
33 | shape: BoxShape.circle,
34 | ),
35 | child: Padding(
36 | padding: const EdgeInsets.all(20.0),
37 | child: Stack(
38 | children: [
39 | Center(
40 | child: Text("${percentComplete.toStringAsFixed(0)}%",
41 | style: TextStyle(color: Colors.white, fontSize: 30))),
42 | CustomPaint(
43 | child: Center(),
44 | painter: ProgressPainter(
45 | circleWidth: 40,
46 | completedPercentage: percentComplete,
47 | defaultCircleColor: Colors.white30,
48 | percentageCompletedCircleColor: Colors.greenAccent,
49 | ),
50 | ),
51 | ],
52 | ),
53 | ),
54 | ),
55 | Container(
56 | child: Padding(
57 | padding: const EdgeInsets.only(top: 15.0),
58 | child: Text("percent saved", style: TextStyle(color: Colors.white)),
59 | ),
60 | ),
61 | ],
62 | ),
63 | ),
64 | ),
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/widgets/trip_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:google_fonts/google_fonts.dart';
4 | import 'package:intl/intl.dart';
5 | import 'package:travel_budget/models/Trip.dart';
6 | import 'package:travel_budget/services/admob_service.dart';
7 | import 'package:travel_budget/views/detail_trip_view.dart';
8 |
9 | Widget buildTripCard(BuildContext context, DocumentSnapshot document, [bool loadBannerAd]) {
10 | final trip = Trip.fromSnapshot(document);
11 | final tripType = trip.types();
12 |
13 | return new Container(
14 | child: Card(
15 | child: InkWell(
16 | child: Padding(
17 | padding: const EdgeInsets.all(16.0),
18 | child: Column(
19 | children: [
20 | Padding(
21 | padding: const EdgeInsets.only(top: 8.0, bottom: 4.0),
22 | child: Row(children: [
23 | Text(
24 | trip.title,
25 | style: GoogleFonts.seymourOne(fontSize: 20.0),
26 | ),
27 | Spacer(),
28 | ]),
29 | ),
30 | Padding(
31 | padding: const EdgeInsets.only(top: 4.0, bottom: 80.0),
32 | child: Row(children: [
33 | Text(
34 | "${DateFormat('MM/dd/yyyy').format(trip.startDate).toString()} - ${DateFormat('MM/dd/yyyy').format(trip.endDate).toString()}"),
35 | Spacer(),
36 | ]),
37 | ),
38 | Padding(
39 | padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
40 | child: Row(
41 | children: [
42 | Text(
43 | "\$${(trip.budget == null) ? "n/a" : trip.budget.toStringAsFixed(2)}",
44 | style: new TextStyle(fontSize: 35.0),
45 | ),
46 | Spacer(),
47 | (tripType.containsKey(trip.travelType)) ? tripType[trip.travelType] : tripType["other"],
48 | ],
49 | ),
50 | )
51 | ],
52 | ),
53 | ),
54 | onTap: () {
55 | if (loadBannerAd == true) {
56 | Navigator.push(context, MaterialPageRoute(builder: (context) => DetailTripView(trip: trip))).then((value) {
57 | AdMobService.showHomeBannerAd();
58 | });
59 | } else {
60 | Navigator.push(context, MaterialPageRoute(builder: (context) => DetailTripView(trip: trip)));
61 | }
62 | },
63 | ),
64 | ),
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/lib/views/home_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 | import 'package:travel_budget/views/home_widgets/home_header.dart';
4 | import 'package:travel_budget/views/home_widgets/percent_saved.dart';
5 | import 'package:travel_budget/views/home_widgets/saved_vs_needed.dart';
6 | import 'package:travel_budget/views/home_widgets/travel_type.dart';
7 | import 'package:travel_budget/views/home_widgets/trip_details_card.dart';
8 |
9 | import 'home_widgets/current_daily_budget.dart';
10 | import 'home_widgets/days_until_trip.dart';
11 | import 'home_widgets/notes.dart';
12 |
13 | class HomeView extends StatefulWidget {
14 | final Trip trip;
15 |
16 | HomeView({
17 | @required this.trip,
18 | });
19 |
20 | @override
21 | _HomeViewState createState() => _HomeViewState();
22 | }
23 |
24 | class _HomeViewState extends State {
25 | @override
26 | Widget build(BuildContext context) {
27 | return SingleChildScrollView(
28 | child: Column(
29 | children: [
30 | HomeHeader(widget.trip),
31 | TripDetailsCard(widget.trip),
32 | Padding(
33 | padding: const EdgeInsets.only(left: 15.0, right: 15.0),
34 | child: IntrinsicHeight(
35 | child: Row(
36 | children: [
37 | Expanded(
38 | flex: 12,
39 | child: DaysUntilTrip(widget.trip),
40 | ),
41 | Spacer(),
42 | Expanded(
43 | flex: 12,
44 | child: CurrentDailyBudget(widget.trip),
45 | )
46 | ],
47 | ),
48 | ),
49 | ),
50 | Padding(
51 | padding: const EdgeInsets.all(15.0),
52 | child: SavedVsNeeded(widget.trip),
53 | ),
54 |
55 | Padding(
56 | padding: const EdgeInsets.only(left: 15.0, right: 15.0),
57 | child: IntrinsicHeight(
58 | child: Row(
59 | children: [
60 | Expanded(
61 | flex: 5,
62 | child: TravelType(widget.trip),
63 | ),
64 | Spacer(),
65 | Expanded(
66 | flex: 12,
67 | child: PercentSaved(widget.trip),
68 | )
69 | ],
70 | ),
71 | ),
72 | ),
73 |
74 | Padding(
75 | padding: const EdgeInsets.all(15.0),
76 | child: Notes(trip: widget.trip),
77 | ),
78 |
79 | Container(height: 40)
80 | ],
81 | ),
82 | );
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 | apply plugin: 'com.google.firebase.crashlytics'
28 |
29 | def keystoreProperties = new Properties()
30 | def keystorePropertiesFile = rootProject.file('key.properties')
31 | if (keystorePropertiesFile.exists()) {
32 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
33 | }
34 |
35 | android {
36 | compileSdkVersion 28
37 |
38 | sourceSets {
39 | main.java.srcDirs += 'src/main/kotlin'
40 | }
41 |
42 | lintOptions {
43 | disable 'InvalidPackage'
44 | }
45 |
46 | defaultConfig {
47 | applicationId "com.a1manstartup.travel_budget"
48 | minSdkVersion 21
49 | targetSdkVersion 29
50 | versionCode flutterVersionCode.toInteger()
51 | versionName flutterVersionName
52 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
53 | // TODO: find a better solution to the DEX reference limit https://developer.android.com/studio/build/multidex
54 | multiDexEnabled true
55 | }
56 |
57 | signingConfigs {
58 | release {
59 | keyAlias keystoreProperties['keyAlias']
60 | keyPassword keystoreProperties['keyPassword']
61 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
62 | storePassword keystoreProperties['storePassword']
63 | }
64 | }
65 |
66 | buildTypes {
67 | release {
68 | signingConfig signingConfigs.release
69 | }
70 | }
71 | }
72 |
73 | flutter {
74 | source '../..'
75 | }
76 |
77 | dependencies {
78 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
79 | testImplementation 'junit:junit:4.12'
80 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
81 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
82 | implementation 'com.android.support:multidex:1.0.3'
83 | }
84 |
85 |
86 | apply plugin: 'com.google.gms.google-services'
--------------------------------------------------------------------------------
/lib/views/home_widgets/trip_details_card.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_size_text/auto_size_text.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:intl/intl.dart';
4 | import 'package:travel_budget/models/Trip.dart';
5 |
6 | class TripDetailsCard extends StatelessWidget {
7 | TripDetailsCard(this.trip);
8 | final Trip trip;
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Container(
13 | child: Stack(
14 | overflow: Overflow.visible,
15 | alignment: Alignment.bottomCenter,
16 | children: [
17 | Container(
18 | child: Column(
19 | children: [
20 | SizedBox(
21 | width: double.infinity,
22 | height: 250,
23 | child: trip.getLocationImage(),
24 | ),
25 | SizedBox(
26 | height: 50,
27 | width: double.infinity,
28 | )
29 | ],
30 | ),
31 | ),
32 | Positioned(
33 | top: 150,
34 | left: 15,
35 | right: 15,
36 | child: Card(
37 | child: Padding(
38 | padding: const EdgeInsets.all(16.0),
39 | child: Column(
40 | children: [
41 | Row(
42 | children: [
43 | Expanded(
44 | child: Container(
45 | height: 40,
46 | child: AutoSizeText(
47 | trip.title,
48 | style: TextStyle(fontSize: 30.0),
49 | maxLines: 2,
50 | ),
51 | ),
52 | )
53 | ],
54 | ),
55 | Row(
56 | children: [
57 | Padding(
58 | padding: const EdgeInsets.only(bottom: 8.0),
59 | child: Text(
60 | "${DateFormat('MMM dd, yyyy').format(trip.startDate).toString()} - ${DateFormat('MMM dd, yyyy').format(trip.endDate).toString()}"),
61 | ),
62 | ],
63 | ),
64 | Row(
65 | children: [
66 | Text("\$${(trip.budget.floor() * trip.getTotalTripDays()).toString()}",
67 | style: TextStyle(fontSize: 25.0, fontWeight: FontWeight.bold)),
68 | Padding(
69 | padding: const EdgeInsets.only(top: 8.0, left: 4.0),
70 | child: Text("total budget"),
71 | ),
72 | ],
73 | )
74 | ],
75 | ),
76 | ),
77 | ),
78 | )
79 | ],
80 | ),
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/views/new_trips/summary_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 | import 'package:cloud_firestore/cloud_firestore.dart';
4 | import 'package:travel_budget/widgets/provider_widget.dart';
5 | import 'package:intl/intl.dart';
6 | import 'package:travel_budget/services/admob_service.dart';
7 | import 'package:firebase_admob/firebase_admob.dart';
8 |
9 |
10 | class NewTripSummaryView extends StatelessWidget {
11 | final db = FirebaseFirestore.instance;
12 | final Trip trip;
13 | final ams = AdMobService();
14 |
15 | NewTripSummaryView({Key key, @required this.trip}) : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | InterstitialAd newTripAd = ams.getNewTripInterstitial();
20 | newTripAd.load();
21 | final tripTypes = trip.types();
22 | var tripKeys = tripTypes.keys.toList();
23 | return Scaffold(
24 | appBar: AppBar(
25 | title: Text('Trip Summary'),
26 | ),
27 | body: Center(
28 | child: Column(
29 | mainAxisAlignment: MainAxisAlignment.center,
30 | children: [
31 | Padding(
32 | padding: const EdgeInsets.all(12.0),
33 | child: Text("Submit", style: TextStyle(fontSize: 20, color: Colors.green),),
34 | ),
35 | Text("${trip.title}"),
36 | Text("${DateFormat('dd/MM/yyyy').format(trip.startDate).toString()} - ${DateFormat('dd/MM/yyyy').format(trip.endDate).toString()}"),
37 | Text("\$${trip.budget.toStringAsFixed(2)}"),
38 |
39 | Expanded(
40 | child: GridView.count(
41 | crossAxisCount: 3,
42 | scrollDirection: Axis.vertical,
43 | primary: false,
44 | children: List.generate(tripTypes.length, (index) {
45 | return FlatButton(
46 | child: Column(
47 | mainAxisAlignment: MainAxisAlignment.center,
48 | children: [
49 | tripTypes[tripKeys[index]],
50 | Text(tripKeys[index]),
51 | ],
52 | ),
53 | onPressed: () async {
54 | trip.travelType = tripKeys[index];
55 | final uid = await Provider.of(context).auth.getCurrentUID();
56 | await db.collection("userData").doc(uid).collection("trips").add(trip.toJson());
57 | newTripAd.show(
58 | anchorType: AnchorType.bottom,
59 | anchorOffset: 0.0,
60 | horizontalCenterOffset: 0.0,
61 | );
62 | Navigator.of(context).popUntil((route) => route.isFirst);
63 | },
64 | );
65 | }),
66 | ),
67 | ),
68 | ],
69 | )
70 | )
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/views/navigation_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/services/firebase_service.dart';
3 | import 'package:travel_budget/views/home_view.dart';
4 | import 'deposit_view.dart';
5 | import 'profile_view.dart';
6 | import 'package:travel_budget/models/Trip.dart';
7 |
8 | class NavigationView extends StatefulWidget {
9 | @override
10 | State createState() {
11 | return _NavigationViewState();
12 | }
13 | }
14 |
15 | class _NavigationViewState extends State {
16 | Future _nextTrip;
17 | Trip _trip;
18 | int _currentIndex = 0;
19 |
20 | @override
21 | void didChangeDependencies() {
22 | super.didChangeDependencies();
23 | _nextTrip = FirebaseService.getNextTrip(context);
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return FutureBuilder(
29 | future: _nextTrip,
30 | builder: (context, snapshot) {
31 | if (snapshot.connectionState == ConnectionState.done) {
32 | if(snapshot.hasData) {
33 | _trip = snapshot.data;
34 | return _buildView();
35 | } else {
36 | // TODO update when user has no trips
37 | return Container(
38 | color: Colors.white,
39 | child: Center(
40 | child: CircularProgressIndicator(),
41 | ),
42 | );
43 | }
44 | } else {
45 | return Container(
46 | color: Colors.white,
47 | child: Center(
48 | child: CircularProgressIndicator(),
49 | ),
50 | );
51 | }
52 | },
53 | );
54 | }
55 |
56 | _buildView() {
57 | final List _children = [
58 | HomeView(trip: _trip),
59 | DepositView(trip: _trip),
60 | ProfileView(),
61 | ];
62 |
63 | return Scaffold(
64 | body: _children[_currentIndex],
65 | floatingActionButton: FloatingActionButton(
66 | onPressed: () {
67 | onTabTapped(1);
68 | },
69 | tooltip: "Add Savings",
70 | child: Icon(Icons.attach_money, color: Colors.indigo),
71 | elevation: 4.0,
72 | backgroundColor: Colors.white,
73 | ),
74 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
75 | bottomNavigationBar: BottomNavigationBar(
76 | onTap: onTabTapped,
77 | currentIndex: _currentIndex,
78 | items: [
79 | BottomNavigationBarItem(
80 | icon: new Icon(Icons.home),
81 | title: new Text("Home"),
82 | ),
83 | BottomNavigationBarItem(
84 | icon: new Icon(Icons.attach_money),
85 | title: new Text("Save"),
86 | ),
87 | BottomNavigationBarItem(
88 | icon: new Icon(Icons.account_circle),
89 | title: new Text("Profile"),
90 | ),
91 | ]
92 | ),
93 | );
94 | }
95 |
96 | void onTabTapped(int index) {
97 | setState(() {
98 | _currentIndex = index;
99 | });
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/lib/views/home_widgets/saved_vs_needed.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 |
4 | class SavedVsNeeded extends StatelessWidget {
5 | SavedVsNeeded(this.trip);
6 | final Trip trip;
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | final saved = (trip.saved ?? 0.0).floor();
11 | final totalBudget = trip.budget.floor() * trip.getTotalTripDays();
12 | final needed = (totalBudget - saved).floor();
13 |
14 | return Card(
15 | child: Container (
16 | height: MediaQuery.of(context).size.height * 0.30,
17 | decoration: BoxDecoration(
18 | borderRadius: BorderRadius.circular(4.0),
19 | gradient: LinearGradient(
20 | colors: [Colors.blueAccent, Colors.indigo],
21 | begin: Alignment.topLeft,
22 | end: Alignment.bottomRight,
23 | ),
24 | ),
25 |
26 | child: Row(
27 | crossAxisAlignment: CrossAxisAlignment.end,
28 | mainAxisAlignment: MainAxisAlignment.spaceAround,
29 | children: [
30 | Column(
31 | children: [
32 | Padding(
33 | padding: const EdgeInsets.only(top: 15.0, bottom: 15.0),
34 | child: Text("saved", style: TextStyle(color: Colors.white)),
35 | ),
36 | Expanded(
37 | child: Container(
38 | alignment: Alignment.bottomCenter,
39 | child: FractionallySizedBox(
40 | heightFactor: (saved > totalBudget) ? 1 : (saved/totalBudget),
41 | child: Container(
42 | width: 50,
43 | color: Colors.white,
44 | ),
45 | ),
46 | ),
47 | ),
48 | Padding(
49 | padding: const EdgeInsets.only(top: 15.0, bottom: 15.0),
50 | child: Text("\$$saved", style: TextStyle(color: Colors.white)),
51 | ),
52 | ],
53 | ),
54 | Column(
55 | children: [
56 | Padding(
57 | padding: const EdgeInsets.only(top: 15.0, bottom: 15.0),
58 | child: Text("needed", style: TextStyle(color: Colors.white)),
59 | ),
60 | Expanded(
61 | child: Container(
62 | alignment: Alignment.bottomCenter,
63 | child: FractionallySizedBox(
64 | heightFactor: (needed <= 0) ? 0 : (needed/totalBudget),
65 | child: Container(
66 | width: 50,
67 | color: Colors.white,
68 | ),
69 | ),
70 | ),
71 | ),
72 | Padding(
73 | padding: const EdgeInsets.only(top: 15.0, bottom: 15.0),
74 | child: Text("\$${(needed <= 0) ? 0 : needed}", style: TextStyle(color: Colors.white)),
75 | ),
76 | ],
77 | )
78 | ],
79 | ),
80 | ),
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/views/past_trips_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:travel_budget/models/Trip.dart';
4 | import 'package:travel_budget/widgets/provider_widget.dart';
5 | import 'package:travel_budget/widgets/trip_card.dart';
6 |
7 |
8 | class PastTripsView extends StatefulWidget {
9 | @override
10 | _PastTripsViewState createState() => _PastTripsViewState();
11 | }
12 |
13 | class _PastTripsViewState extends State {
14 | TextEditingController _searchController = TextEditingController();
15 |
16 | Future resultsLoaded;
17 | List _allResults = [];
18 | List _resultsList = [];
19 |
20 | @override
21 | void initState() {
22 | super.initState();
23 | _searchController.addListener(_onSearchChanged);
24 | }
25 |
26 | @override
27 | void dispose() {
28 | _searchController.removeListener(_onSearchChanged);
29 | _searchController.dispose();
30 | super.dispose();
31 | }
32 |
33 | @override
34 | void didChangeDependencies() {
35 | super.didChangeDependencies();
36 | resultsLoaded = getUsersPastTripsStreamSnapshots();
37 | }
38 |
39 |
40 | _onSearchChanged() {
41 | searchResultsList();
42 | }
43 |
44 | searchResultsList() {
45 | var showResults = [];
46 |
47 | if(_searchController.text != "") {
48 | for(var tripSnapshot in _allResults){
49 | var title = Trip.fromSnapshot(tripSnapshot).title.toLowerCase();
50 |
51 | if(title.contains(_searchController.text.toLowerCase())) {
52 | showResults.add(tripSnapshot);
53 | }
54 | }
55 |
56 | } else {
57 | showResults = List.from(_allResults);
58 | }
59 | setState(() {
60 | _resultsList = showResults;
61 | });
62 | }
63 |
64 | getUsersPastTripsStreamSnapshots() async {
65 | final uid = await Provider.of(context).auth.getCurrentUID();
66 | var data = await FirebaseFirestore.instance
67 | .collection('userData')
68 | .doc(uid)
69 | .collection('trips')
70 | .where("endDate", isLessThanOrEqualTo: DateTime.now())
71 | .orderBy('endDate')
72 | .get();
73 | setState(() {
74 | _allResults = data.docs;
75 | });
76 | searchResultsList();
77 | return "complete";
78 | }
79 |
80 | Widget build(BuildContext context) {
81 | return Container(
82 | child: Column(
83 | children: [
84 | Text("Past Trips", style: TextStyle(fontSize: 20)),
85 | Padding(
86 | padding: const EdgeInsets.only(left: 30.0, right: 30.0, bottom: 30.0),
87 | child: TextField(
88 | controller: _searchController,
89 | decoration: InputDecoration(
90 | prefixIcon: Icon(Icons.search)
91 | ),
92 | ),
93 | ),
94 | Expanded(
95 | child: ListView.builder(
96 | itemCount: _resultsList.length,
97 | itemBuilder: (BuildContext context, int index) =>
98 | buildTripCard(context, _resultsList[index]),
99 | )
100 |
101 | ),
102 | ],
103 | ),
104 | );
105 | }
106 | }
--------------------------------------------------------------------------------
/lib/views/first_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:auto_size_text/auto_size_text.dart';
3 | import 'package:travel_budget/widgets/custom_dialog.dart';
4 |
5 | class FirstView extends StatelessWidget {
6 | final primaryColor = const Color(0xFF75A2EA);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | final _width = MediaQuery.of(context).size.width;
11 | final _height = MediaQuery.of(context).size.height;
12 |
13 | return Scaffold(
14 | body: Container(
15 | width: _width,
16 | height: _height,
17 | color: primaryColor,
18 | child: SafeArea(
19 | child: Padding(
20 | padding: const EdgeInsets.all(16.0),
21 | child: Column(
22 | children: [
23 | SizedBox(height: _height * 0.10),
24 | Text(
25 | "Welcome",
26 | style: TextStyle(fontSize: 44, color: Colors.white),
27 | ),
28 | SizedBox(height: _height * 0.10),
29 | AutoSizeText(
30 | "Let’s start planning your next trip",
31 | maxLines: 2,
32 | textAlign: TextAlign.center,
33 | style: TextStyle(
34 | fontSize: 40,
35 | color: Colors.white,
36 | ),
37 | ),
38 | SizedBox(height: _height * 0.15),
39 | RaisedButton(
40 | color: Colors.white,
41 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)),
42 | child: Padding(
43 | padding: const EdgeInsets.only(top: 10.0, bottom: 10.0, left: 30.0, right: 30.0),
44 | child: Text(
45 | "Get Started",
46 | style: TextStyle(
47 | color: primaryColor,
48 | fontSize: 28,
49 | fontWeight: FontWeight.w300,
50 | ),
51 | ),
52 | ),
53 | onPressed: () {
54 | showDialog(
55 | context: context,
56 | builder: (BuildContext context) => CustomDialog(
57 | title: "Would you like to create a free account?",
58 | description:
59 | "With an account, your data will be securely saved, allowing you to access it from multiple devices.",
60 | primaryButtonText: "Create My Account",
61 | primaryButtonRoute: "/signUp",
62 | secondaryButtonText: "Maybe Later",
63 | secondaryButtonRoute: "/anonymousSignIn",
64 | ),
65 | );
66 | },
67 | ),
68 | SizedBox(height: _height * 0.05),
69 | FlatButton(
70 | child: Text(
71 | "Sign In",
72 | style: TextStyle(color: Colors.white, fontSize: 25),
73 | ),
74 | onPressed: () {
75 | Navigator.of(context).pushReplacementNamed('/signIn');
76 | },
77 | )
78 | ],
79 | ),
80 | ),
81 | ),
82 | ),
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/models/Trip.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:cloud_firestore/cloud_firestore.dart';
3 | import 'package:travel_budget/credentials.dart';
4 |
5 |
6 | class Trip {
7 | String title;
8 | DateTime startDate;
9 | DateTime endDate;
10 | double budget;
11 | Map budgetTypes;
12 | String travelType;
13 | String photoReference;
14 | String notes;
15 | String documentId;
16 | double saved;
17 | List ledger;
18 |
19 |
20 | Trip(
21 | this.title,
22 | this.startDate,
23 | this.endDate,
24 | this.budget,
25 | this.budgetTypes,
26 | this.travelType
27 | );
28 |
29 | // formatting for upload to Firbase when creating the trip
30 | Map toJson() => {
31 | 'title': title,
32 | 'startDate': startDate,
33 | 'endDate': endDate,
34 | 'budget': budget,
35 | 'budgetTypes': budgetTypes,
36 | 'travelType': travelType,
37 | 'photoReference': photoReference,
38 | };
39 |
40 | // creating a Trip object from a firebase snapshot
41 | Trip.fromSnapshot(DocumentSnapshot snapshot) :
42 | title = snapshot.data()['title'],
43 | startDate = snapshot.data()['startDate'].toDate(),
44 | endDate = snapshot.data()['endDate'].toDate(),
45 | budget = snapshot.data()['budget'],
46 | budgetTypes = snapshot.data()['budgetTypes'],
47 | travelType = snapshot.data()['travelType'],
48 | photoReference = snapshot.data()['photoReference'],
49 | notes = snapshot.data()['notes'],
50 | documentId = snapshot.id,
51 | saved = snapshot.data()['saved'],
52 | ledger = snapshot.data()['ledger'];
53 |
54 |
55 |
56 | Map types({color = Colors.black}) => {
57 | "car": Icon(Icons.directions_car, size: 50, color: color),
58 | "bus": Icon(Icons.directions_bus, size: 50, color: color),
59 | "train": Icon(Icons.train, size: 50, color: color),
60 | "plane": Icon(Icons.airplanemode_active, size: 50, color: color),
61 | "ship": Icon(Icons.directions_boat, size: 50, color: color),
62 | "other": Icon(Icons.directions, size: 50, color: color),
63 | };
64 |
65 | // return the google places image
66 | Image getLocationImage() {
67 | final baseUrl = "https://maps.googleapis.com/maps/api/place/photo";
68 | final maxWidth = "1000";
69 | final url = "$baseUrl?maxwidth=$maxWidth&photoreference=$photoReference&key=$PLACES_API_KEY";
70 | return Image.network(url, fit: BoxFit.cover);
71 | }
72 |
73 | int getTotalTripDays() {
74 | int total = endDate.difference(startDate).inDays;
75 | if (total < 1) {
76 | total = 1;
77 | }
78 | return total;
79 | }
80 |
81 | int getDaysUntilTrip() {
82 | int diff = startDate.difference(DateTime.now()).inDays;
83 | if (diff < 0) {
84 | diff = 0;
85 | }
86 | return diff;
87 | }
88 |
89 | int getCurrentDailyBudget() {
90 | if (saved == 0 || saved == null) {
91 | return 0;
92 | } else {
93 | return (saved / getTotalTripDays()).floor();
94 | }
95 | }
96 |
97 | Map ledgerItem(String amount, String type) {
98 | var amountDouble = double.parse(amount);
99 | if (type == "spent") {
100 | amountDouble = double.parse("-" + amount);
101 | }
102 | return {
103 | 'ledger': FieldValue.arrayUnion([
104 | {
105 | "date": DateTime.now(),
106 | "amount": amountDouble,
107 | },
108 | ]),
109 | 'saved': FieldValue.increment(amountDouble)
110 | };
111 | }
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:firebase_core/firebase_core.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:google_fonts/google_fonts.dart';
5 | import 'package:travel_budget/services/custom_colors.dart';
6 | import 'package:travel_budget/views/navigation_view.dart';
7 | import 'package:travel_budget/views/first_view.dart';
8 | import 'package:travel_budget/views/sign_up_view.dart';
9 | import 'package:travel_budget/widgets/provider_widget.dart';
10 | import 'package:travel_budget/services/auth_service.dart';
11 | import 'package:firebase_admob/firebase_admob.dart';
12 | import 'package:travel_budget/services/admob_service.dart';
13 | import 'package:firebase_crashlytics/firebase_crashlytics.dart';
14 |
15 | void main() async {
16 | WidgetsFlutterBinding.ensureInitialized();
17 | await Firebase.initializeApp();
18 | FirebaseAdMob.instance.initialize(appId: AdMobService().getAdMobAppId());
19 | FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
20 | runApp(MyApp());
21 | }
22 |
23 | class MyApp extends StatefulWidget {
24 | @override
25 | _MyAppState createState() => _MyAppState();
26 | }
27 |
28 | class _MyAppState extends State with WidgetsBindingObserver {
29 | var colors = CustomColors(WidgetsBinding.instance.window.platformBrightness);
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | WidgetsBinding.instance.addObserver(this);
35 | }
36 |
37 | @override
38 | void dispose() {
39 | WidgetsBinding.instance.removeObserver(this);
40 | super.dispose();
41 | }
42 |
43 | @override
44 | void didChangePlatformBrightness() {
45 | setState(() {
46 | colors = CustomColors(WidgetsBinding.instance.window.platformBrightness);
47 | });
48 | }
49 |
50 | @override
51 | Widget build(BuildContext context) {
52 | return Provider(
53 | auth: AuthService(),
54 | db: FirebaseFirestore.instance,
55 | colors: colors,
56 | child: MaterialApp(
57 | debugShowCheckedModeBanner: false,
58 | title: "Travel Budget App",
59 | theme: ThemeData(
60 | brightness: Brightness.light,
61 | primarySwatch: Colors.blue,
62 | textTheme: TextTheme(bodyText2: GoogleFonts.quicksand(fontSize: 14.0))),
63 | darkTheme: ThemeData(
64 | brightness: Brightness.dark,
65 | primarySwatch: Colors.blue,
66 | textTheme: TextTheme(bodyText2: GoogleFonts.bitter(fontSize: 14.0))),
67 | home: HomeController(),
68 | routes: {
69 | '/home': (BuildContext context) => HomeController(),
70 | '/signUp': (BuildContext context) => SignUpView(authFormType: AuthFormType.signUp),
71 | '/signIn': (BuildContext context) => SignUpView(authFormType: AuthFormType.signIn),
72 | '/anonymousSignIn': (BuildContext context) => SignUpView(authFormType: AuthFormType.anonymous),
73 | '/convertUser': (BuildContext context) => SignUpView(authFormType: AuthFormType.convert),
74 | },
75 | ),
76 | );
77 | }
78 | }
79 |
80 | class HomeController extends StatelessWidget {
81 | @override
82 | Widget build(BuildContext context) {
83 | final AuthService auth = Provider.of(context).auth;
84 | return StreamBuilder(
85 | stream: auth.onAuthStateChanged,
86 | builder: (context, AsyncSnapshot snapshot) {
87 | if (snapshot.connectionState == ConnectionState.active) {
88 | final bool signedIn = snapshot.hasData;
89 | return signedIn ? NavigationView() : FirstView();
90 | }
91 | return Container();
92 | },
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/lib/views/edit_notes_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 | import 'package:cloud_firestore/cloud_firestore.dart';
4 | import 'package:travel_budget/widgets/provider_widget.dart';
5 | import 'package:travel_budget/widgets/rounded_button.dart';
6 |
7 | class EditNotesView extends StatefulWidget {
8 | final Trip trip;
9 |
10 | EditNotesView({Key key, @required this.trip}) : super(key: key);
11 |
12 | @override
13 | _EditNotesViewState createState() => _EditNotesViewState();
14 | }
15 |
16 | class _EditNotesViewState extends State {
17 | TextEditingController _notesController = new TextEditingController();
18 |
19 | final db = FirebaseFirestore.instance;
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | _notesController.text = widget.trip.notes;
25 | }
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | return Scaffold(
30 | body: Container(
31 | height: MediaQuery.of(context).size.height,
32 | width: MediaQuery.of(context).size.width,
33 | color: Colors.amberAccent,
34 | child: Hero(
35 | tag: "TripNotes-${widget.trip.title}",
36 | transitionOnUserGestures: true,
37 | child: SafeArea(
38 | child: SingleChildScrollView(
39 | child: Column(
40 | children: [
41 | buildHeading(context),
42 | buildNotesText(),
43 | buildSubmitButton(context),
44 | ],
45 | ),
46 | ),
47 | ),
48 | ),
49 | ),
50 | );
51 | }
52 |
53 | Widget buildHeading(context) {
54 | return Material(
55 | color: Colors.amberAccent,
56 | child: Padding(
57 | padding: const EdgeInsets.only(left: 20.0, top: 10.0),
58 | child: Row(
59 | children: [
60 | Expanded(
61 | child: Text(
62 | "Trip Notes",
63 | style: TextStyle(fontSize: 24, color: Colors.black),
64 | ),
65 | ),
66 | FlatButton(
67 | child: Icon(Icons.close, color: Colors.black, size: 30),
68 | onPressed: () {
69 | Navigator.of(context).pop();
70 | },
71 | )
72 | ],
73 | ),
74 | ),
75 | );
76 | }
77 |
78 | Widget buildNotesText() {
79 | return Material(
80 | color: Colors.amberAccent,
81 | child: Padding(
82 | padding: const EdgeInsets.all(20.0),
83 | child: TextField(
84 | maxLines: null,
85 | controller: _notesController,
86 | decoration: InputDecoration(
87 | border: InputBorder.none,
88 | ),
89 | cursorColor: Colors.black,
90 | autofocus: true,
91 | style: TextStyle(color: Colors.black),
92 | ),
93 | ),
94 | );
95 | }
96 |
97 | Widget buildSubmitButton(context) {
98 | return Material(
99 | color: Colors.amberAccent,
100 | child: RoundedButton(
101 | child: Text("Save", style: TextStyle(color: Colors.white)),
102 | color: Colors.indigo,
103 | onPressed: () async {
104 | widget.trip.notes = _notesController.text;
105 |
106 | final uid = Provider.of(context).auth.getCurrentUID();
107 |
108 | await db.collection("userData")
109 | .doc(uid)
110 | .collection("trips")
111 | .doc(widget.trip.documentId)
112 | .update({'notes': _notesController.text});
113 |
114 | Navigator.of(context).pop();
115 |
116 | },
117 | ),
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: travel_budget
2 | description: A new Flutter application.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # In Android, build-name is used as versionName while build-number used as versionCode.
10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
12 | # Read more about iOS versioning at
13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14 | version: 1.0.0+1
15 |
16 | environment:
17 | sdk: ">=2.1.0 <3.0.0"
18 |
19 | dependencies:
20 | flutter:
21 | sdk: flutter
22 |
23 | firebase_core: ^0.5.3
24 | cloud_firestore: ^0.14.0
25 | firebase_auth: ^0.18.0
26 | google_sign_in: ^4.0.4
27 | apple_sign_in: ^0.1.0
28 |
29 | # The following adds the Cupertino Icons font to your application.
30 | # Use with the CupertinoIcons class for iOS style icons.
31 | cupertino_icons: ^0.1.2
32 | intl: 0.16.1
33 | date_range_picker: ^1.0.5
34 | auto_size_text: ^2.0.2
35 | flutter_spinkit: ^3.1.0
36 | flutter_auth_buttons: ^0.5.0
37 | dio: ^3.0.10
38 | uuid: 2.0.1
39 | device_info: ^0.4.1+4
40 | google_fonts: ^0.3.2
41 | firebase_admob: ^0.10.0-dev.1
42 | flare_flutter: ^2.0.3
43 | international_phone_input: ^1.0.4
44 | flutter_launcher_icons: "^0.7.3"
45 | firebase_crashlytics: "^0.2.4"
46 |
47 |
48 | # flutter pub run flutter_launcher_icons:main
49 | flutter_icons:
50 | image_path: "assets/icons/main_logo.png"
51 | adaptive_icon_foreground: "assets/icons/foreground_logo.png"
52 | adaptive_icon_background: "#57AEAF"
53 | android: true
54 | ios: true
55 |
56 |
57 |
58 | dev_dependencies:
59 | flutter_test:
60 | sdk: flutter
61 |
62 |
63 | # For information on the generic Dart part of this file, see the
64 | # following page: https://www.dartlang.org/tools/pub/pubspec
65 |
66 | # The following section is specific to Flutter.
67 | flutter:
68 |
69 | # The following line ensures that the Material Icons font is
70 | # included with your application, so that you can use the icons in
71 | # the material Icons class.
72 | uses-material-design: true
73 |
74 | # To add assets to your application, add an assets section, like this:
75 | assets:
76 | - assets/
77 | # - images/a_dot_burr.jpeg
78 | # - images/a_dot_ham.jpeg
79 |
80 | # An image asset can refer to one or more resolution-specific "variants", see
81 | # https://flutter.dev/assets-and-images/#resolution-aware.
82 |
83 | # For details regarding adding assets from package dependencies, see
84 | # https://flutter.dev/assets-and-images/#from-packages
85 |
86 | # To add custom fonts to your application, add a fonts section here,
87 | # in this "flutter" section. Each entry in this list should have a
88 | # "family" key with the font family name, and a "fonts" key with a
89 | # list giving the asset and other descriptors for the font. For
90 | # example:
91 | # fonts:
92 | # - family: Schyler
93 | # fonts:
94 | # - asset: fonts/Schyler-Regular.ttf
95 | # - asset: fonts/Schyler-Italic.ttf
96 | # style: italic
97 | # - family: Trajan Pro
98 | # fonts:
99 | # - asset: fonts/TrajanPro.ttf
100 | # - asset: fonts/TrajanPro_Bold.ttf
101 | # weight: 700
102 | #
103 | # For details regarding fonts from package dependencies,
104 | # see https://flutter.dev/custom-fonts/#from-packages
105 |
--------------------------------------------------------------------------------
/lib/generated/i18n.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | // ignore_for_file: non_constant_identifier_names
7 | // ignore_for_file: camel_case_types
8 | // ignore_for_file: prefer_single_quotes
9 |
10 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost.
11 | class S implements WidgetsLocalizations {
12 | const S();
13 |
14 | static S current;
15 |
16 | static const GeneratedLocalizationsDelegate delegate =
17 | GeneratedLocalizationsDelegate();
18 |
19 | static S of(BuildContext context) => Localizations.of(context, S);
20 |
21 | @override
22 | TextDirection get textDirection => TextDirection.ltr;
23 |
24 | }
25 |
26 | class $en extends S {
27 | const $en();
28 | }
29 |
30 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate {
31 | const GeneratedLocalizationsDelegate();
32 |
33 | List get supportedLocales {
34 | return const [
35 | Locale("en", ""),
36 | ];
37 | }
38 |
39 | LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) {
40 | return (List locales, Iterable supported) {
41 | if (locales == null || locales.isEmpty) {
42 | return fallback ?? supported.first;
43 | } else {
44 | return _resolve(locales.first, fallback, supported, withCountry);
45 | }
46 | };
47 | }
48 |
49 | LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) {
50 | return (Locale locale, Iterable supported) {
51 | return _resolve(locale, fallback, supported, withCountry);
52 | };
53 | }
54 |
55 | @override
56 | Future load(Locale locale) {
57 | final String lang = getLang(locale);
58 | if (lang != null) {
59 | switch (lang) {
60 | case "en":
61 | S.current = const $en();
62 | return SynchronousFuture(S.current);
63 | default:
64 | // NO-OP.
65 | }
66 | }
67 | S.current = const S();
68 | return SynchronousFuture(S.current);
69 | }
70 |
71 | @override
72 | bool isSupported(Locale locale) => _isSupported(locale, true);
73 |
74 | @override
75 | bool shouldReload(GeneratedLocalizationsDelegate old) => false;
76 |
77 | ///
78 | /// Internal method to resolve a locale from a list of locales.
79 | ///
80 | Locale _resolve(Locale locale, Locale fallback, Iterable supported, bool withCountry) {
81 | if (locale == null || !_isSupported(locale, withCountry)) {
82 | return fallback ?? supported.first;
83 | }
84 |
85 | final Locale languageLocale = Locale(locale.languageCode, "");
86 | if (supported.contains(locale)) {
87 | return locale;
88 | } else if (supported.contains(languageLocale)) {
89 | return languageLocale;
90 | } else {
91 | final Locale fallbackLocale = fallback ?? supported.first;
92 | return fallbackLocale;
93 | }
94 | }
95 |
96 | ///
97 | /// Returns true if the specified locale is supported, false otherwise.
98 | ///
99 | bool _isSupported(Locale locale, bool withCountry) {
100 | if (locale != null) {
101 | for (Locale supportedLocale in supportedLocales) {
102 | // Language must always match both locales.
103 | if (supportedLocale.languageCode != locale.languageCode) {
104 | continue;
105 | }
106 |
107 | // If country code matches, return this locale.
108 | if (supportedLocale.countryCode == locale.countryCode) {
109 | return true;
110 | }
111 |
112 | // If no country requirement is requested, check if this locale has no country.
113 | if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) {
114 | return true;
115 | }
116 | }
117 | }
118 | return false;
119 | }
120 | }
121 |
122 | String getLang(Locale l) => l == null
123 | ? null
124 | : l.countryCode != null && l.countryCode.isEmpty
125 | ? l.languageCode
126 | : l.toString();
127 |
--------------------------------------------------------------------------------
/lib/widgets/custom_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:auto_size_text/auto_size_text.dart';
3 |
4 | class CustomDialog extends StatelessWidget {
5 | final primaryColor = const Color(0xFF75A2EA);
6 | final grayColor = const Color(0xFF939393);
7 |
8 | final String title,
9 | description,
10 | primaryButtonText,
11 | primaryButtonRoute,
12 | secondaryButtonText,
13 | secondaryButtonRoute;
14 |
15 | CustomDialog(
16 | {@required this.title,
17 | @required this.description,
18 | @required this.primaryButtonText,
19 | @required this.primaryButtonRoute,
20 | this.secondaryButtonText,
21 | this.secondaryButtonRoute});
22 |
23 | static const double padding = 20.0;
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return Dialog(
28 | shape: RoundedRectangleBorder(
29 | borderRadius: BorderRadius.circular(padding),
30 | ),
31 | child: Stack(
32 | children: [
33 | Container(
34 | padding: EdgeInsets.all(padding),
35 | decoration: BoxDecoration(
36 | color: Colors.white,
37 | shape: BoxShape.rectangle,
38 | borderRadius: BorderRadius.circular(padding),
39 | boxShadow: [
40 | BoxShadow(
41 | color: Colors.black,
42 | blurRadius: 10.0,
43 | offset: const Offset(0.0, 10.0),
44 | ),
45 | ]),
46 | child: Column(
47 | mainAxisSize: MainAxisSize.min,
48 | children: [
49 | SizedBox(height: 24.0),
50 | AutoSizeText(
51 | title,
52 | maxLines: 2,
53 | textAlign: TextAlign.center,
54 | style: TextStyle(
55 | color: primaryColor,
56 | fontSize: 25.0,
57 | ),
58 | ),
59 | SizedBox(height: 24.0),
60 | AutoSizeText(
61 | description,
62 | maxLines: 4,
63 | textAlign: TextAlign.center,
64 | style: TextStyle(
65 | color: grayColor,
66 | fontSize: 18.0,
67 | ),
68 | ),
69 | SizedBox(height: 24.0),
70 | RaisedButton(
71 | color: primaryColor,
72 | shape: RoundedRectangleBorder(
73 | borderRadius: BorderRadius.circular(30.0)),
74 | child: Padding(
75 | padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
76 | child: AutoSizeText(
77 | primaryButtonText,
78 | maxLines: 1,
79 | style: TextStyle(
80 | fontSize: 18,
81 | fontWeight: FontWeight.w200,
82 | color: Colors.white,
83 | ),
84 | ),
85 | ),
86 | onPressed: () {
87 | Navigator.of(context).pop();
88 | Navigator.of(context)
89 | .pushReplacementNamed(primaryButtonRoute);
90 | },
91 | ),
92 | SizedBox(height: 10.0),
93 | showSecondaryButton(context),
94 | ],
95 | ),
96 | )
97 | ],
98 | ),
99 | );
100 | }
101 |
102 | showSecondaryButton(BuildContext context) {
103 | if (secondaryButtonRoute != null && secondaryButtonText != null ){
104 | return FlatButton(
105 | child: AutoSizeText(
106 | secondaryButtonText,
107 | maxLines: 1,
108 | style: TextStyle(
109 | fontSize: 18,
110 | color: primaryColor,
111 | fontWeight: FontWeight.w400,
112 | ),
113 | ),
114 | onPressed: () {
115 | Navigator.of(context).pop();
116 | Navigator.of(context).pushReplacementNamed(secondaryButtonRoute);
117 | },
118 | );
119 | } else {
120 | return SizedBox(height: 10.0);
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/lib/views/deposit_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:travel_budget/models/Trip.dart';
4 | import 'package:travel_budget/services/firebase_service.dart';
5 | import 'package:travel_budget/views/navigation_view.dart';
6 | import 'package:travel_budget/widgets/provider_widget.dart';
7 | import 'package:travel_budget/widgets/rounded_button.dart';
8 |
9 | class DepositView extends StatefulWidget {
10 | final Trip trip;
11 |
12 | DepositView({
13 | @required this.trip,
14 | });
15 |
16 | @override
17 | _DepositViewState createState() => _DepositViewState();
18 | }
19 |
20 | class _DepositViewState extends State {
21 | String _amount = "0";
22 | String _error;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return Container(
27 | color: Colors.indigo,
28 | width: MediaQuery.of(context).size.width,
29 | height: MediaQuery.of(context).size.height,
30 | child: SafeArea(
31 | child: Column(
32 | children: [
33 | Spacer(),
34 | FittedBox(
35 | fit: BoxFit.fitWidth,
36 | child: Text(
37 | "\$$_amount",
38 | style: TextStyle(fontSize: 100, fontWeight: FontWeight.bold, color: Colors.white),
39 | ),
40 | ),
41 | Padding(
42 | padding: const EdgeInsets.only(top: 15.0),
43 | child: Text("${_error ?? ''}", style: TextStyle(color: Colors.white)),
44 | ),
45 | Container(
46 | child: Padding(
47 | padding: const EdgeInsets.symmetric(horizontal: 25.0),
48 | child: GridView.count(
49 | crossAxisCount: 3,
50 | shrinkWrap: true,
51 | physics: NeverScrollableScrollPhysics(),
52 | childAspectRatio: 1.4,
53 | children: setKeyboard(),
54 | ),
55 | ),
56 | ),
57 | Spacer(),
58 | Padding(
59 | padding: const EdgeInsets.symmetric(horizontal: 15.0),
60 | child: Row(
61 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
62 | children: [
63 | _actionBtn("spent"),
64 | _actionBtn("saved"),
65 | ],
66 | ),
67 | ),
68 | Spacer()
69 | ],
70 | ),
71 | ),
72 | );
73 | }
74 |
75 | Widget _numberBtn(String number) {
76 | return FlatButton(
77 | child: Text(
78 | "$number",
79 | style: TextStyle(fontSize: 40, color: Colors.white),
80 | ),
81 | onPressed: () {
82 | setState(() {
83 | if (_amount == "0") {
84 | _amount = "$number";
85 | } else if (_amount.length == 5) {
86 | _amount = _amount;
87 | HapticFeedback.heavyImpact();
88 | } else {
89 | _amount += "$number";
90 | }
91 | });
92 | },
93 | );
94 | }
95 |
96 | Widget _deleteBtn() {
97 | return FlatButton(
98 | child: Text(
99 | "<",
100 | style: TextStyle(fontSize: 40, color: Colors.white),
101 | ),
102 | onPressed: () {
103 | setState(() {
104 | if (_amount.length <= 1) {
105 | _amount = "0";
106 | } else {
107 | _amount = _amount.substring(0, _amount.length - 1);
108 | }
109 | });
110 | },
111 | );
112 | }
113 |
114 | Widget _actionBtn(String type) {
115 | return Expanded(
116 | child: Padding(
117 | padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
118 | child: RoundedButton(
119 | color: Colors.indigoAccent,
120 | child:
121 | Text("${type[0].toUpperCase()}${type.substring(1)}", style: TextStyle(color: Colors.white, fontSize: 20)),
122 | onPressed: () async {
123 | if (_amount == "0") {
124 | setState(() {
125 | _error = "Enter an amount";
126 | });
127 | } else if (type == "spent" && double.parse(_amount) > widget.trip.saved) {
128 | setState(() {
129 | _error = "Yove've only saved \$${widget.trip.saved.floor()}";
130 | });
131 | } else {
132 | FirebaseService.addToLedger(context, widget.trip.documentId, widget.trip.ledgerItem(_amount, type));
133 | Navigator.pushReplacement(
134 | context,
135 | PageRouteBuilder(
136 | pageBuilder: (_, __, ___) => NavigationView(),
137 | transitionDuration: Duration(seconds: 0),
138 | ),
139 | );
140 | }
141 | },
142 | ),
143 | ),
144 | );
145 | }
146 |
147 | setKeyboard() {
148 | List keyboard = [];
149 |
150 | // numbers 1-9
151 | List.generate(9, (index) {
152 | keyboard.add(_numberBtn("${index + 1}"));
153 | });
154 |
155 | keyboard.add(Text(""));
156 | keyboard.add(_numberBtn("0"));
157 | keyboard.add(_deleteBtn());
158 |
159 | return keyboard;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/.flutter-plugins-dependencies:
--------------------------------------------------------------------------------
1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"apple_sign_in","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/apple_sign_in-0.1.0/","dependencies":[]},{"name":"cloud_firestore","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.14.4/","dependencies":["firebase_core"]},{"name":"device_info","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-0.4.2+7/","dependencies":[]},{"name":"firebase_admob","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_admob-0.10.0-dev.1/","dependencies":["firebase_core"]},{"name":"firebase_auth","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth-0.18.4+1/","dependencies":["firebase_core"]},{"name":"firebase_core","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-0.5.3/","dependencies":[]},{"name":"firebase_crashlytics","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_crashlytics-0.2.4/","dependencies":["firebase_core"]},{"name":"google_sign_in","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/google_sign_in-4.5.3/","dependencies":[]},{"name":"libphonenumber","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/libphonenumber-1.0.1/","dependencies":[]},{"name":"path_provider","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.14/","dependencies":[]}],"android":[{"name":"apple_sign_in","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/apple_sign_in-0.1.0/","dependencies":[]},{"name":"cloud_firestore","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.14.4/","dependencies":["firebase_core"]},{"name":"device_info","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/device_info-0.4.2+7/","dependencies":[]},{"name":"firebase_admob","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_admob-0.10.0-dev.1/","dependencies":["firebase_core"]},{"name":"firebase_auth","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth-0.18.4+1/","dependencies":["firebase_core"]},{"name":"firebase_core","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-0.5.3/","dependencies":[]},{"name":"firebase_crashlytics","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_crashlytics-0.2.4/","dependencies":["firebase_core"]},{"name":"google_sign_in","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/google_sign_in-4.5.3/","dependencies":[]},{"name":"libphonenumber","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/libphonenumber-1.0.1/","dependencies":[]},{"name":"path_provider","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.14/","dependencies":[]}],"macos":[{"name":"cloud_firestore","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.14.4/","dependencies":["firebase_core"]},{"name":"firebase_auth","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth-0.18.4+1/","dependencies":["firebase_core"]},{"name":"firebase_core","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-0.5.3/","dependencies":[]},{"name":"firebase_crashlytics","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_crashlytics-0.2.4/","dependencies":["firebase_core"]},{"name":"path_provider_macos","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+3/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]}],"windows":[],"web":[{"name":"cloud_firestore_web","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore_web-0.2.1+2/","dependencies":["firebase_core_web"]},{"name":"firebase_auth_web","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_auth_web-0.3.2+3/","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core_web-0.2.1+1/","dependencies":[]},{"name":"google_sign_in_web","path":"/Users/davefaliskie/flutter/.pub-cache/hosted/pub.dartlang.org/google_sign_in_web-0.9.1+1/","dependencies":[]}]},"dependencyGraph":[{"name":"apple_sign_in","dependencies":[]},{"name":"cloud_firestore","dependencies":["firebase_core","cloud_firestore_web"]},{"name":"cloud_firestore_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"device_info","dependencies":[]},{"name":"firebase_admob","dependencies":["firebase_core"]},{"name":"firebase_auth","dependencies":["firebase_core","firebase_auth_web"]},{"name":"firebase_auth_web","dependencies":["firebase_core","firebase_core_web"]},{"name":"firebase_core","dependencies":["firebase_core_web"]},{"name":"firebase_core_web","dependencies":[]},{"name":"firebase_crashlytics","dependencies":["firebase_core"]},{"name":"google_sign_in","dependencies":["google_sign_in_web"]},{"name":"google_sign_in_web","dependencies":[]},{"name":"libphonenumber","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]}],"date_created":"2021-03-07 14:57:33.645272","version":"1.20.3"}
--------------------------------------------------------------------------------
/lib/views/new_trips/budget_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 | import 'package:travel_budget/widgets/divider_with_text_widget.dart';
4 | import 'package:travel_budget/widgets/money_text_field.dart';
5 | import 'summary_view.dart';
6 |
7 | enum budgetType { simple, complex }
8 |
9 | class NewTripBudgetView extends StatefulWidget {
10 | final Trip trip;
11 |
12 | NewTripBudgetView({Key key, @required this.trip}) : super(key: key);
13 |
14 | @override
15 | _NewTripBudgetViewState createState() => _NewTripBudgetViewState();
16 | }
17 |
18 | class _NewTripBudgetViewState extends State {
19 | var _budgetState = budgetType.simple;
20 | var _switchButtonText = "Build Budget";
21 | var _budgetTotal = 0;
22 |
23 | TextEditingController _budgetController = new TextEditingController();
24 | TextEditingController _transportationController = new TextEditingController();
25 | TextEditingController _foodController = new TextEditingController();
26 | TextEditingController _lodgingController = new TextEditingController();
27 | TextEditingController _entertainmentController = new TextEditingController();
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 | _budgetController.addListener(_setBudgetTotal);
33 | _transportationController.addListener(_setTotalBudget);
34 | _foodController.addListener(_setTotalBudget);
35 | _lodgingController.addListener(_setTotalBudget);
36 | _entertainmentController.addListener(_setTotalBudget);
37 | }
38 |
39 |
40 | _setTotalBudget() {
41 | var total = 0;
42 | total = (_transportationController.text == "") ? 0 : int.parse(_transportationController.text);
43 | total += (_foodController.text == "") ? 0 : int.parse(_foodController.text);
44 | total += (_lodgingController.text== "") ? 0 : int.parse(_lodgingController.text);
45 | total += (_entertainmentController.text == "") ? 0 : int.parse(_entertainmentController.text);
46 | setState(() {
47 | _budgetTotal = total;
48 | });
49 | }
50 |
51 | _setBudgetTotal() {
52 | setState(() {
53 | _budgetTotal = (_budgetController.text == "") ? 0 : int.parse(_budgetController.text);
54 | });
55 | }
56 |
57 |
58 | List setBudgetFields(_budgetController) {
59 | List fields = [];
60 |
61 | if (_budgetState == budgetType.simple) {
62 | _switchButtonText = "Build Budget";
63 | fields.add(Padding(
64 | padding: const EdgeInsets.all(12.0),
65 | child: Text("Enter a Trip Budget"),
66 | ));
67 | fields.add(MoneyTextField(controller: _budgetController, helperText: "Daily estimated budget"));
68 | } else {
69 | // assumes complex budget
70 | _switchButtonText = "Simple Budget";
71 | fields.add(Padding(
72 | padding: const EdgeInsets.all(12.0),
73 | child: Text("Enter How much you want to spend in each area"),
74 | ));
75 | fields.add(MoneyTextField(controller: _transportationController, helperText: "Daily Estimated Transportation Budget"));
76 | fields.add(MoneyTextField(controller: _foodController, helperText: "Daily Estimated Food Budget"));
77 | fields.add(MoneyTextField(controller: _lodgingController, helperText: "Daily Estimated Lodging Budget"));
78 | fields.add(MoneyTextField(controller: _entertainmentController, helperText: "Daily Estimated Entertainment Budget"));
79 | fields.add(Text("Total: \$$_budgetTotal"));
80 | }
81 |
82 | fields.add(FlatButton(
83 | child: Text(
84 | "Continue",
85 | style: TextStyle(fontSize: 25, color: Colors.blue),
86 | ),
87 | onPressed: () async {
88 | widget.trip.budget = _budgetTotal.toDouble();
89 | widget.trip.budgetTypes = {
90 | 'transportation': (_transportationController.text == "") ? 0.0 : double.parse(_transportationController.text),
91 | 'food': (_foodController.text == "") ? 0.0 : double.parse(_foodController.text),
92 | 'lodging': (_lodgingController.text== "") ? 0.0 : double.parse(_lodgingController.text),
93 | 'entertainment': (_entertainmentController.text == "") ? 0.0 : double.parse(_entertainmentController.text),
94 | };
95 |
96 | Navigator.push(
97 | context,
98 | MaterialPageRoute(
99 | builder: (context) => NewTripSummaryView(trip: widget.trip)),
100 | );
101 | },
102 | ));
103 | fields.add(DividerWithText(dividerText: "or"));
104 | fields.add(FlatButton(
105 | child: Text(
106 | _switchButtonText,
107 | style: TextStyle(fontSize: 25, color: Colors.blue),
108 | ),
109 | onPressed: () {
110 | setState(() {
111 | _budgetState = (_budgetState == budgetType.simple)
112 | ? budgetType.complex
113 | : budgetType.simple;
114 | });
115 | },
116 | ));
117 |
118 | return fields;
119 | }
120 |
121 | @override
122 | Widget build(BuildContext context) {
123 | _budgetController.text = (_budgetController.text == "") ? "" : _budgetTotal.toString();
124 | _budgetController.selection = TextSelection.collapsed(offset: _budgetController.text.length);
125 |
126 | return Scaffold(
127 | appBar: AppBar(
128 | title: Text('Create Trip - Budget'),
129 | ),
130 | body: SingleChildScrollView(
131 | child: Center(
132 | child: Column(
133 | mainAxisAlignment: MainAxisAlignment.center,
134 | children: setBudgetFields(_budgetController),
135 | ),
136 | ),
137 | ),
138 | );
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/lib/widgets/calculator_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/widgets/money_text_field.dart';
3 | import 'package:travel_budget/models/Trip.dart';
4 | import 'package:travel_budget/widgets/provider_widget.dart';
5 | import 'package:cloud_firestore/cloud_firestore.dart';
6 |
7 | class CalculatorWidget extends StatefulWidget {
8 | final Trip trip;
9 |
10 | CalculatorWidget({
11 | @required this.trip,
12 | });
13 |
14 | @override
15 | _CalculatorWidgetState createState() => _CalculatorWidgetState();
16 | }
17 |
18 | class _CalculatorWidgetState extends State {
19 | TextEditingController _moneyController = TextEditingController();
20 | int _saved;
21 | int _needed;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | _saved = (widget.trip.saved ?? 0.0).floor();
27 | _needed = (widget.trip.budget.floor() * widget.trip.getTotalTripDays()) - _saved;
28 | }
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | return Card(
33 | child: Column(
34 | children: [
35 | Container(
36 | color: Colors.cyan,
37 | child: Padding(
38 | padding: const EdgeInsets.only(top: 12.0, bottom: 12.0),
39 | child: Row(
40 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
41 | children: [
42 | Column(
43 | children: [
44 | Text("\$$_saved", style: TextStyle(fontSize: 60)),
45 | Text("Saved", style: TextStyle(fontSize: 20)),
46 | ],
47 | ),
48 | Container(
49 | height: 80,
50 | child: VerticalDivider(
51 | color: Colors.white,
52 | thickness: 5,
53 | ),
54 | ),
55 | Column(
56 | children: [
57 | Text("\$$_needed", style: TextStyle(fontSize: 60)),
58 | Text("Needed", style: TextStyle(fontSize: 20)),
59 | ],
60 | ),
61 | ],
62 | ),
63 | ),
64 | ),
65 | Container(
66 | color: Colors.orangeAccent,
67 | child: Padding(
68 | padding: const EdgeInsets.only(left: 20.0, right: 40.0),
69 | child: Row(
70 | children: [
71 | Expanded(
72 | child: MoneyTextField(
73 | controller: _moneyController,
74 | helperText: "Save Additional",
75 | ),
76 | ),
77 | IconButton(
78 | icon: Icon(Icons.add_circle),
79 | color: Colors.green,
80 | iconSize: 50,
81 | onPressed: () async {
82 | setState(() {
83 | _saved = _saved + int.parse(_moneyController.text);
84 | _needed = _needed - int.parse(_moneyController.text);
85 | });
86 | final uid = await Provider.of(context).auth.getCurrentUID();
87 | await FirebaseFirestore.instance.collection('userData')
88 | .doc(uid)
89 | .collection('trips')
90 | .doc(widget.trip.documentId)
91 | .update({'saved': _saved.toDouble()});
92 | },
93 | ),
94 | IconButton(
95 | icon: Icon(Icons.remove_circle),
96 | color: Colors.red,
97 | iconSize: 50,
98 | onPressed: () async {
99 | setState(() {
100 | _saved = _saved - int.parse(_moneyController.text);
101 | _needed = _needed + int.parse(_moneyController.text);
102 | });
103 | final uid = await Provider.of(context).auth.getCurrentUID();
104 | await FirebaseFirestore.instance.collection('userData')
105 | .doc(uid)
106 | .collection('trips')
107 | .doc(widget.trip.documentId)
108 | .update({'saved': _saved.toDouble()});
109 | },
110 | )
111 | ],
112 | ),
113 | ),
114 | ),
115 | Container(
116 | color: Colors.orangeAccent,
117 | child: Padding(
118 | padding: const EdgeInsets.only(bottom: 20.0),
119 | child: Row(
120 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
121 | children: [
122 | generateAddMoneyBtn(25),
123 | generateAddMoneyBtn(50),
124 | generateAddMoneyBtn(100),
125 | ],
126 | ),
127 | ),
128 | )
129 | ],
130 | ),
131 | );
132 | }
133 |
134 |
135 | RaisedButton generateAddMoneyBtn(int amount) {
136 | return RaisedButton(
137 | child: Text("\$$amount"),
138 | color: Colors.white,
139 | textColor: Colors.deepOrange,
140 | onPressed: () async {
141 | setState(() {
142 | _saved = _saved + amount;
143 | _needed = _needed - amount;
144 | });
145 | final uid = await Provider.of(context).auth.getCurrentUID();
146 | await FirebaseFirestore.instance.collection('userData')
147 | .doc(uid)
148 | .collection('trips')
149 | .doc(widget.trip.documentId)
150 | .update({'saved': _saved.toDouble()});
151 | },
152 | );
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/lib/views/new_trips/location_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 | import 'package:travel_budget/models/Place.dart';
4 | import 'package:travel_budget/widgets/divider_with_text_widget.dart';
5 | import 'date_view.dart';
6 | import 'package:travel_budget/credentials.dart';
7 | import 'package:dio/dio.dart';
8 | import 'package:auto_size_text/auto_size_text.dart';
9 | import 'dart:async';
10 | import 'package:uuid/uuid.dart';
11 |
12 |
13 | class NewTripLocationView extends StatefulWidget {
14 | final Trip trip;
15 |
16 | NewTripLocationView({Key key, @required this.trip}) : super(key: key);
17 |
18 | @override
19 | _NewTripLocationViewState createState() => _NewTripLocationViewState();
20 | }
21 |
22 | class _NewTripLocationViewState extends State {
23 | TextEditingController _searchController = new TextEditingController();
24 | // Timer _throttle;
25 | var uuid = new Uuid();
26 | String _sessionToken;
27 |
28 | String _heading;
29 | List _placesList;
30 | final List _suggestedList = [
31 | // Place("New York", 320.00),
32 | // Place("Austin", 250.00),
33 | // Place("Boston", 290.00),
34 | // Place("Florence", 300.00),
35 | // Place("Washington D.C.", 190.00),
36 | ];
37 |
38 | @override
39 | void initState() {
40 | super.initState();
41 | _heading = "Suggestions";
42 | _placesList = _suggestedList;
43 | _searchController.addListener(_onSearchChanged);
44 | }
45 |
46 | @override
47 | void dispose() {
48 | _searchController.removeListener(_onSearchChanged);
49 | _searchController.dispose();
50 | super.dispose();
51 | }
52 |
53 | _onSearchChanged() {
54 | if(_sessionToken == null) {
55 | setState(() {
56 | _sessionToken = uuid.v4();
57 | });
58 | }
59 | getLocationResults(_searchController.text);
60 | }
61 |
62 | void getLocationResults(String input) async {
63 | if (input.isEmpty) {
64 | setState(() {
65 | _heading = "Suggestions";
66 | });
67 | return;
68 | }
69 |
70 | String baseURL = 'https://maps.googleapis.com/maps/api/place/autocomplete/json';
71 | String type = '(regions)';
72 | String request = '$baseURL?input=$input&key=$PLACES_API_KEY&type=$type&sessiontoken=$_sessionToken';
73 | Response response = await Dio().get(request);
74 |
75 | final predictions = response.data['predictions'];
76 |
77 | List _displayResults = [];
78 |
79 | for (var i=0; i < predictions.length; i++) {
80 | String name = predictions[i]['description'];
81 | String placeId = predictions[i]['place_id'];
82 |
83 | // TODO figure out the budget
84 | double averageBudget = 200.0;
85 | _displayResults.add(Place(name, averageBudget, placeId));
86 | }
87 |
88 | setState(() {
89 | _heading = "Results";
90 | _placesList = _displayResults;
91 | });
92 | }
93 |
94 | Future getLocationPhotoRef(placeId) async {
95 | String placeImgRequest = 'https://maps.googleapis.com/maps/api/place/details/json?place_id=$placeId&fields=photo,geometry&key=$PLACES_API_KEY&sessiontoken=$_sessionToken';
96 | Response placeDetails = await Dio().get(placeImgRequest);
97 | return placeDetails.data["result"]["photos"][0]["photo_reference"];
98 | }
99 |
100 | @override
101 | Widget build(BuildContext context) {
102 | return Scaffold(
103 | appBar: AppBar(
104 | title: Text('Create Trip - Location'),
105 | ),
106 | body: Center(
107 | child: Column(
108 | children: [
109 | Padding(
110 | padding: const EdgeInsets.all(30.0),
111 | child: TextField(
112 | controller: _searchController,
113 | decoration: InputDecoration(
114 | prefixIcon: Icon(Icons.search),
115 | ),
116 | ),
117 | ),
118 | Padding(
119 | padding: const EdgeInsets.only(left: 10.0, right: 10.0),
120 | child: new DividerWithText(
121 | dividerText: _heading,
122 | ),
123 | ),
124 | Expanded(
125 | child: ListView.builder(
126 | itemCount: _placesList.length,
127 | itemBuilder: (BuildContext context, int index) =>
128 | buildPlaceCard(context, index),
129 | ),
130 | ),
131 | ],
132 | ),
133 | ),
134 | );
135 | }
136 |
137 | Widget buildPlaceCard(BuildContext context, int index) {
138 | return Hero(
139 | tag: "SelectedTrip-${_placesList[index].name}",
140 | transitionOnUserGestures: true,
141 | child: Container(
142 | child: Padding(
143 | padding: const EdgeInsets.only(
144 | left: 8.0,
145 | right: 8.0,
146 | ),
147 | child: Card(
148 | child: InkWell(
149 | child: Row(
150 | children: [
151 | Expanded(
152 | child: Padding(
153 | padding: const EdgeInsets.all(16.0),
154 | child: Column(
155 | children: [
156 | Row(
157 | children: [
158 | Flexible(
159 | child: AutoSizeText(_placesList[index].name,
160 | maxLines: 3,
161 | style: TextStyle(fontSize: 25.0)),
162 | ),
163 | ],
164 | ),
165 | Row(
166 | children: [
167 | Text(
168 | "Average Budget \$${_placesList[index].averageBudget.toStringAsFixed(2)}"),
169 | ],
170 | ),
171 | ],
172 | ),
173 | ),
174 | ),
175 |
176 | ],
177 | ),
178 | onTap: () async {
179 | String photoReference = await getLocationPhotoRef(_placesList[index].placeId);
180 | widget.trip.title = _placesList[index].name;
181 | widget.trip.photoReference = photoReference;
182 | setState(() {
183 | _sessionToken = null;
184 | });
185 | // TODO maybe pass the trip average budget through here too...
186 | // that would need to be added to the Trip object
187 | Navigator.push(
188 | context,
189 | MaterialPageRoute(
190 | builder: (context) => NewTripDateView(trip: widget.trip)),
191 | );
192 | },
193 | ),
194 | ),
195 | ),
196 | ),
197 | );
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/lib/views/new_trips/date_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/models/Trip.dart';
3 | import 'package:date_range_picker/date_range_picker.dart' as DateRagePicker;
4 | import 'package:intl/intl.dart';
5 | import 'package:auto_size_text/auto_size_text.dart';
6 | import 'dart:async';
7 | import 'budget_view.dart';
8 |
9 | class NewTripDateView extends StatefulWidget {
10 | final Trip trip;
11 |
12 | NewTripDateView({Key key, @required this.trip}) : super(key: key);
13 |
14 | @override
15 | _NewTripDateViewState createState() => _NewTripDateViewState();
16 | }
17 |
18 | class _NewTripDateViewState extends State {
19 | DateTime _startDate = DateTime.now();
20 | DateTime _endDate = DateTime.now().add(Duration(days: 7));
21 |
22 | Future displayDateRangePicker(BuildContext context) async {
23 | final List picked = await DateRagePicker.showDatePicker(
24 | context: context,
25 | initialFirstDate: _startDate,
26 | initialLastDate: _endDate,
27 | firstDate: new DateTime(DateTime.now().year - 50),
28 | lastDate: new DateTime(DateTime.now().year + 50));
29 | if (picked != null && picked.length == 2) {
30 | setState(() {
31 | _startDate = picked[0];
32 | _endDate = picked[1];
33 | });
34 | }
35 | }
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | return Scaffold(
40 | body: Center(
41 | child: CustomScrollView(
42 | slivers: [
43 | SliverAppBar(
44 | title: Text(''),
45 | backgroundColor: Colors.green,
46 | expandedHeight: 350.0,
47 | flexibleSpace: FlexibleSpaceBar(
48 | background: widget.trip.getLocationImage(),
49 | ),
50 | ),
51 | SliverFixedExtentList(
52 | itemExtent: 200.00,
53 | delegate: SliverChildListDelegate([
54 | buildSelectedDetails(context, widget.trip),
55 | buildButtons(),
56 | ]),
57 | )
58 | ],
59 | ),
60 | ),
61 | );
62 | }
63 |
64 | Widget buildButtons() {
65 | return Column(
66 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
67 | children: [
68 | SizedBox(
69 | width: MediaQuery.of(context).size.width * 0.60,
70 | child: RaisedButton(
71 | child: Text("Change Date Range"),
72 | color: Colors.deepPurpleAccent,
73 | textColor: Colors.white,
74 | onPressed: () async {
75 | await displayDateRangePicker(context);
76 | },
77 | ),
78 | ),
79 | SizedBox(
80 | width: MediaQuery.of(context).size.width * 0.60,
81 | child: RaisedButton(
82 | child: Text('Continue'),
83 | color: Colors.amberAccent,
84 | onPressed: () {
85 | widget.trip.startDate = _startDate;
86 | widget.trip.endDate = _endDate;
87 | Navigator.push(
88 | context,
89 | MaterialPageRoute(
90 | builder: (context) => NewTripBudgetView(
91 | trip: widget.trip,
92 | ),
93 | ),
94 | );
95 | },
96 | ),
97 | ),
98 | ],
99 | );
100 | }
101 |
102 | Widget buildingSelectedDates() {
103 | return Padding(
104 | padding: const EdgeInsets.only(top: 15.0),
105 | child: Container(
106 | child: Row(
107 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
108 | children: [
109 | Column(
110 | mainAxisAlignment: MainAxisAlignment.center,
111 | crossAxisAlignment: CrossAxisAlignment.center,
112 | children: [
113 | Text("Start Date"),
114 | Padding(
115 | padding: const EdgeInsets.only(top: 8.0),
116 | child: Text(
117 | "${DateFormat('MM-dd').format(_startDate).toString()}",
118 | style: TextStyle(fontSize: 35, color: Colors.deepPurple),
119 | ),
120 | ),
121 | Text(
122 | "${DateFormat('yyyy').format(_startDate).toString()}",
123 | style: TextStyle(color: Colors.deepPurple),
124 | ),
125 | ],
126 | ),
127 | Container(
128 | child: Icon(
129 | Icons.arrow_forward,
130 | color: Colors.deepOrange,
131 | size: 45,
132 | )),
133 | Column(
134 | mainAxisAlignment: MainAxisAlignment.center,
135 | crossAxisAlignment: CrossAxisAlignment.center,
136 | children: [
137 | Text("End Date"),
138 | Padding(
139 | padding: const EdgeInsets.only(top: 8.0),
140 | child: Text(
141 | "${DateFormat('MM-dd').format(_endDate).toString()}",
142 | style: TextStyle(fontSize: 35, color: Colors.deepPurple),
143 | ),
144 | ),
145 | Text(
146 | "${DateFormat('yyyy').format(_endDate).toString()}",
147 | style: TextStyle(color: Colors.deepPurple),
148 | ),
149 | ],
150 | ),
151 | ],
152 | ),
153 | ),
154 | );
155 | }
156 |
157 | Widget buildSelectedDetails(BuildContext context, Trip trip) {
158 | return Hero(
159 | tag: "SelectedTrip-${trip.title}",
160 | transitionOnUserGestures: true,
161 | child: Container(
162 | child: Padding(
163 | padding: const EdgeInsets.only(
164 | left: 8.0,
165 | right: 8.0,
166 | ),
167 | child: SingleChildScrollView(
168 | child: Card(
169 | child: Row(
170 | children: [
171 | Expanded(
172 | child: Padding(
173 | padding: const EdgeInsets.only(
174 | top: 16.0, left: 16.0, bottom: 16.0),
175 | child: Column(
176 | children: [
177 | Row(
178 | children: [
179 | Flexible(
180 | child: AutoSizeText(trip.title,
181 | maxLines: 3,
182 | style: TextStyle(fontSize: 25.0)),
183 | ),
184 | ],
185 | ),
186 | buildingSelectedDates(),
187 | ],
188 | ),
189 | ),
190 | ),
191 | ],
192 | ),
193 | ),
194 | ),
195 | ),
196 | ),
197 | );
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/lib/views/profile_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:travel_budget/widgets/provider_widget.dart';
3 | import 'package:intl/intl.dart';
4 | import 'package:travel_budget/models/User.dart';
5 |
6 | class ProfileView extends StatefulWidget {
7 | @override
8 | _ProfileViewState createState() => _ProfileViewState();
9 | }
10 |
11 | class _ProfileViewState extends State {
12 | User user = User("");
13 | bool _isAdmin = false;
14 | TextEditingController _userCountryController = TextEditingController();
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return SingleChildScrollView(
19 | child: Container(
20 | width: MediaQuery.of(context).size.width,
21 | child: Column(
22 | children: [
23 | FutureBuilder(
24 | future: Provider.of(context).auth.getCurrentUser(),
25 | builder: (context, snapshot) {
26 | if (snapshot.connectionState == ConnectionState.done) {
27 | return displayUserInformation(context, snapshot);
28 | } else {
29 | return CircularProgressIndicator();
30 | }
31 | },
32 | )
33 | ],
34 | ),
35 | ),
36 | );
37 | }
38 |
39 | Widget displayUserInformation(context, snapshot) {
40 | final authData = snapshot.data;
41 |
42 | return Column(
43 | children: [
44 | Padding(
45 | padding: const EdgeInsets.only(top: 10.0),
46 | child: Provider.of(context).auth.getProfileImage(),
47 | ),
48 | Padding(
49 | padding: const EdgeInsets.all(8.0),
50 | child: Text(
51 | "Name: ${authData.displayName ?? 'Anonymous'}",
52 | style: TextStyle(fontSize: 20),
53 | ),
54 | ),
55 | Padding(
56 | padding: const EdgeInsets.all(8.0),
57 | child: Text(
58 | "Email: ${authData.email ?? 'Anonymous'}",
59 | style: TextStyle(fontSize: 20),
60 | ),
61 | ),
62 | Padding(
63 | padding: const EdgeInsets.all(8.0),
64 | child: Text(
65 | "Created: ${DateFormat('MM/dd/yyyy').format(authData.metadata.creationTime)}",
66 | style: TextStyle(fontSize: 20),
67 | ),
68 | ),
69 | FutureBuilder(
70 | future: _getProfileData(),
71 | builder: (context, snapshot) {
72 | if (snapshot.connectionState == ConnectionState.done) {
73 | _userCountryController.text = user.homeCountry;
74 | _isAdmin = user.admin ?? false;
75 | }
76 | return Container(
77 | child: Column(
78 | children: [
79 | Padding(
80 | padding: const EdgeInsets.all(8.0),
81 | child: Text(
82 | "Home Country: ${_userCountryController.text}",
83 | style: TextStyle(fontSize: 20),
84 | ),
85 | ),
86 | adminFeature(),
87 | ],
88 | ),
89 | );
90 | }
91 | ),
92 | showSignOut(context, authData.isAnonymous),
93 | RaisedButton(
94 | child: Text("Edit User"),
95 | onPressed: () {
96 | _userEditBottomSheet(context);
97 | },
98 | )
99 | ],
100 | );
101 | }
102 |
103 | _getProfileData() async {
104 | final uid = await Provider.of(context).auth.getCurrentUID();
105 | await Provider.of(context)
106 | .db
107 | .collection('userData')
108 | .document(uid)
109 | .get().then((result) {
110 | user.homeCountry = result.data['homeCountry'];
111 | user.admin = result.data['admin'];
112 | });
113 | }
114 |
115 | Widget showSignOut(context, bool isAnonymous) {
116 | if (isAnonymous == true) {
117 | return RaisedButton(
118 | child: Text("Sign In To Save Your Data"),
119 | onPressed: () {
120 | Navigator.of(context).pushNamed('/convertUser');
121 | },
122 | );
123 | } else {
124 | return RaisedButton(
125 | child: Text("Sign Out"),
126 | onPressed: () {
127 | try {
128 | Provider.of(context).auth.signOut();
129 | } catch (e) {
130 | print(e);
131 | }
132 | },
133 | );
134 | }
135 | }
136 |
137 | Widget adminFeature() {
138 | if(_isAdmin == true) {
139 | return Text("You are an admin");
140 | } else {
141 | return Container();
142 | }
143 | }
144 |
145 | void _userEditBottomSheet(BuildContext context) {
146 | showModalBottomSheet(
147 | context: context,
148 | builder: (BuildContext bc) {
149 | return Container(
150 | height: MediaQuery.of(context).size.height * .60,
151 | child: Padding(
152 | padding: const EdgeInsets.only(left: 15.0, top: 15.0),
153 | child: Column(
154 | children: [
155 | Row(
156 | children: [
157 | Text("Update Profile"),
158 | Spacer(),
159 | IconButton(
160 | icon: Icon(Icons.cancel),
161 | color: Colors.orange,
162 | iconSize: 25,
163 | onPressed: () {
164 | Navigator.of(context).pop();
165 | },
166 | ),
167 | ],
168 | ),
169 | Row(
170 | children: [
171 | Expanded(
172 | child: Padding(
173 | padding: const EdgeInsets.only(right: 15.0),
174 | child: TextField(
175 | controller: _userCountryController,
176 | decoration: InputDecoration(
177 | helperText: "Home Country",
178 | ),
179 | ),
180 | ),
181 | )
182 | ],
183 | ),
184 | Row(
185 | mainAxisAlignment: MainAxisAlignment.center,
186 | children: [
187 | RaisedButton(
188 | child: Text('Save'),
189 | color: Colors.green,
190 | textColor: Colors.white,
191 | onPressed: () async {
192 | user.homeCountry = _userCountryController.text;
193 | setState(() {
194 | _userCountryController.text = user.homeCountry;
195 | });
196 | final uid =
197 | await Provider.of(context).auth.getCurrentUID();
198 | await Provider.of(context)
199 | .db
200 | .collection('userData')
201 | .document(uid)
202 | .setData(user.toJson());
203 | Navigator.of(context).pop();
204 | },
205 | )
206 | ],
207 | ),
208 | ],
209 | ),
210 | ),
211 | );
212 | },
213 | );
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/lib/services/auth_service.dart:
--------------------------------------------------------------------------------
1 | import 'package:apple_sign_in/apple_sign_in.dart';
2 | import 'package:firebase_auth/firebase_auth.dart';
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:google_sign_in/google_sign_in.dart';
6 |
7 | class AuthService {
8 | final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
9 | final GoogleSignIn _googleSignIn = GoogleSignIn();
10 |
11 | Stream get onAuthStateChanged => _firebaseAuth.authStateChanges().map(
12 | (User user) => user?.uid,
13 | );
14 |
15 | // GET UID
16 | String getCurrentUID() {
17 | return _firebaseAuth.currentUser.uid;
18 | }
19 |
20 | // GET CURRENT USER
21 | Future getCurrentUser() async {
22 | return _firebaseAuth.currentUser;
23 | }
24 |
25 | getProfileImage() {
26 | if(_firebaseAuth.currentUser.photoURL != null) {
27 | return Image.network(_firebaseAuth.currentUser.photoURL, height: 100, width: 100);
28 | } else {
29 | return Icon(Icons.account_circle, size: 100);
30 | }
31 | }
32 |
33 | // Email & Password Sign Up
34 | Future createUserWithEmailAndPassword(
35 | String email, String password, String name) async {
36 | final authResult = await _firebaseAuth.createUserWithEmailAndPassword(
37 | email: email,
38 | password: password,
39 | );
40 |
41 | // Update the username
42 | await updateUserName(name, authResult.user);
43 | return authResult.user.uid;
44 | }
45 |
46 | Future updateUserName(String name, User currentUser) async {
47 | await currentUser.updateProfile(displayName: name);
48 | await currentUser.reload();
49 | }
50 |
51 | // Email & Password Sign In
52 | Future signInWithEmailAndPassword(
53 | String email, String password) async {
54 | return (await _firebaseAuth.signInWithEmailAndPassword(
55 | email: email, password: password))
56 | .user
57 | .uid;
58 | }
59 |
60 | // Sign Out
61 | signOut() async {
62 | return await _firebaseAuth.signOut();
63 | }
64 |
65 | // Reset Password
66 | Future sendPasswordResetEmail(String email) async {
67 | return _firebaseAuth.sendPasswordResetEmail(email: email);
68 | }
69 |
70 | // Create Anonymous User
71 | Future singInAnonymously() {
72 | return _firebaseAuth.signInAnonymously();
73 | }
74 |
75 | Future convertUserWithEmail(
76 | String email, String password, String name) async {
77 | final currentUser = _firebaseAuth.currentUser;
78 |
79 | final credential =
80 | EmailAuthProvider.credential(email: email, password: password);
81 | await currentUser.linkWithCredential(credential);
82 | await updateUserName(name, currentUser);
83 | }
84 |
85 | Future convertWithGoogle() async {
86 | final currentUser = _firebaseAuth.currentUser;
87 | final GoogleSignInAccount account = await _googleSignIn.signIn();
88 | final GoogleSignInAuthentication _googleAuth = await account.authentication;
89 | final AuthCredential credential = GoogleAuthProvider.credential(
90 | idToken: _googleAuth.idToken,
91 | accessToken: _googleAuth.accessToken,
92 | );
93 | await currentUser.linkWithCredential(credential);
94 | await updateUserName(_googleSignIn.currentUser.displayName, currentUser);
95 | }
96 |
97 | // GOOGLE
98 | Future signInWithGoogle() async {
99 | final GoogleSignInAccount account = await _googleSignIn.signIn();
100 | final GoogleSignInAuthentication _googleAuth = await account.authentication;
101 | final AuthCredential credential = GoogleAuthProvider.credential(
102 | idToken: _googleAuth.idToken,
103 | accessToken: _googleAuth.accessToken,
104 | );
105 | return (await _firebaseAuth.signInWithCredential(credential)).user.uid;
106 | }
107 |
108 | // APPLE
109 | Future signInWithApple() async {
110 | final AuthorizationResult result = await AppleSignIn.performRequests([
111 | AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
112 | ]);
113 |
114 | switch (result.status) {
115 | case AuthorizationStatus.authorized:
116 | final AppleIdCredential _auth = result.credential;
117 | final OAuthProvider oAuthProvider =
118 | new OAuthProvider("apple.com");
119 |
120 | final AuthCredential credential = oAuthProvider.credential(
121 | idToken: String.fromCharCodes(_auth.identityToken),
122 | accessToken: String.fromCharCodes(_auth.authorizationCode),
123 | );
124 |
125 | await _firebaseAuth.signInWithCredential(credential);
126 |
127 | // update the user information
128 | if (_auth.fullName != null) {
129 | await _firebaseAuth.currentUser.updateProfile(displayName: "${_auth.fullName.givenName} ${_auth.fullName.familyName}");
130 | }
131 |
132 | break;
133 |
134 | case AuthorizationStatus.error:
135 | print("Sign In Failed ${result.error.localizedDescription}");
136 | break;
137 |
138 | case AuthorizationStatus.cancelled:
139 | print("User Cancled");
140 | break;
141 | }
142 | }
143 |
144 | Future createUserWithPhone(String phone, BuildContext context) async {
145 | _firebaseAuth.verifyPhoneNumber(
146 | phoneNumber: phone,
147 | timeout: Duration(seconds: 0),
148 | verificationCompleted: (AuthCredential authCredential) {
149 | _firebaseAuth.signInWithCredential(authCredential).then((UserCredential result){
150 | Navigator.of(context).pop(); // to pop the dialog box
151 | Navigator.of(context).pushReplacementNamed('/home');
152 | }).catchError((e) {
153 | return "error";
154 | });
155 | },
156 | verificationFailed: (FirebaseAuthException exception) {
157 | return "error";
158 | },
159 | codeSent: (String verificationId, [int forceResendingToken]) {
160 | final _codeController = TextEditingController();
161 | showDialog(
162 | context: context,
163 | barrierDismissible: false,
164 | builder: (context) => AlertDialog(
165 | title: Text("Enter Verification Code From Text Message"),
166 | content: Column(
167 | mainAxisSize: MainAxisSize.min,
168 | children: [TextField(controller: _codeController)],
169 | ),
170 | actions: [
171 | FlatButton(
172 | child: Text("submit"),
173 | textColor: Colors.white,
174 | color: Colors.green,
175 | onPressed: () {
176 | var _credential = PhoneAuthProvider.credential(verificationId: verificationId,
177 | smsCode: _codeController.text.trim());
178 | _firebaseAuth.signInWithCredential(_credential).then((UserCredential result){
179 | Navigator.of(context).pop(); // to pop the dialog box
180 | Navigator.of(context).pushReplacementNamed('/home');
181 | }).catchError((e) {
182 | return "error";
183 | });
184 | },
185 | )
186 | ],
187 | ),
188 | );
189 | },
190 | codeAutoRetrievalTimeout: (String verificationId) {
191 | verificationId = verificationId;
192 | });
193 | }
194 | }
195 |
196 | class NameValidator {
197 | static String validate(String value) {
198 | if (value.isEmpty) {
199 | return "Name can't be empty";
200 | }
201 | if (value.length < 2) {
202 | return "Name must be at least 2 characters long";
203 | }
204 | if (value.length > 50) {
205 | return "Name must be less than 50 characters long";
206 | }
207 | return null;
208 | }
209 | }
210 |
211 | class EmailValidator {
212 | static String validate(String value) {
213 | if (value.isEmpty) {
214 | return "Email can't be empty";
215 | }
216 | return null;
217 | }
218 | }
219 |
220 | class PasswordValidator {
221 | static String validate(String value) {
222 | if (value.isEmpty) {
223 | return "Password can't be empty";
224 | }
225 | return null;
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/lib/views/detail_trip_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cloud_firestore/cloud_firestore.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:travel_budget/models/Trip.dart';
4 | import 'package:travel_budget/services/admob_service.dart';
5 | import 'package:travel_budget/widgets/provider_widget.dart';
6 | import 'edit_notes_view.dart';
7 | import 'package:intl/intl.dart';
8 | import 'package:auto_size_text/auto_size_text.dart';
9 | import 'package:travel_budget/widgets/money_text_field.dart';
10 | import 'package:travel_budget/widgets/calculator_widget.dart';
11 |
12 | class DetailTripView extends StatefulWidget {
13 | final Trip trip;
14 |
15 | DetailTripView({Key key, @required this.trip}) : super(key: key);
16 |
17 | @override
18 | _DetailTripViewState createState() => _DetailTripViewState();
19 | }
20 |
21 | class _DetailTripViewState extends State {
22 | TextEditingController _budgetController = TextEditingController();
23 | var _budget;
24 |
25 | void initState() {
26 | super.initState();
27 | AdMobService.hideHomeBannerAd();
28 | _budgetController.text = widget.trip.budget.toStringAsFixed(0);
29 | _budget = widget.trip.budget.floor();
30 | }
31 |
32 | @override
33 | Widget build(BuildContext context) {
34 | return Scaffold(
35 | body: Center(
36 | child: CustomScrollView(
37 | slivers: [
38 | SliverAppBar(
39 | title: Text('Trip Details'),
40 | backgroundColor: Colors.green,
41 | expandedHeight: 350.0,
42 | flexibleSpace: FlexibleSpaceBar(
43 | background: widget.trip.getLocationImage(),
44 | ),
45 | actions: [
46 | IconButton(
47 | icon: Icon(
48 | Icons.settings,
49 | color: Colors.white,
50 | size: 30,
51 | ),
52 | padding: const EdgeInsets.only(right: 15),
53 | onPressed: () {
54 | _tripEditModalBottomSheet(context);
55 | },
56 | ),
57 | ],
58 | ),
59 | SliverList(
60 | delegate: SliverChildListDelegate([
61 | tripDetails(),
62 | CalculatorWidget(trip: widget.trip),
63 | totalBudgetCard(),
64 | daysOutCard(),
65 | notesCard(context),
66 | Container(
67 | height: 200,
68 | )
69 | ]),
70 | )
71 | ],
72 | ),
73 | ),
74 | );
75 | }
76 |
77 | Widget daysOutCard() {
78 | return Card(
79 | color: Colors.amberAccent,
80 | child: Padding(
81 | padding: const EdgeInsets.all(8.0),
82 | child: Row(
83 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
84 | children: [
85 | Text("${widget.trip.getDaysUntilTrip()}", style: TextStyle(fontSize: 75)),
86 | Text("days until your trip", style: TextStyle(fontSize: 25))
87 | ],
88 | ),
89 | ),
90 | );
91 | }
92 |
93 | Widget tripDetails() {
94 | return Card(
95 | child: Column(
96 | children: [
97 | Row(
98 | children: [
99 | Padding(
100 | padding: const EdgeInsets.all(8.0),
101 | child: Text(
102 | widget.trip.title,
103 | style: TextStyle(fontSize: 30, color: Colors.green[900]),
104 | ),
105 | ),
106 | ],
107 | ),
108 | Row(
109 | children: [
110 | Padding(
111 | padding: const EdgeInsets.only(left: 8.0, bottom: 8.0),
112 | child: Text(
113 | "${DateFormat('MM/dd/yyyy').format(widget.trip.startDate).toString()} - ${DateFormat('MM/dd/yyyy').format(widget.trip.endDate).toString()}"),
114 | ),
115 | ],
116 | ),
117 | ],
118 | ),
119 | );
120 | }
121 |
122 | Widget totalBudgetCard() {
123 | return Card(
124 | color: Colors.blue,
125 | child: Column(
126 | children: [
127 | Padding(
128 | padding: const EdgeInsets.all(8.0),
129 | child: Row(
130 | mainAxisAlignment: MainAxisAlignment.center,
131 | children: [
132 | Text("Daily Budget",
133 | style: TextStyle(fontSize: 15, color: Colors.white)),
134 | ],
135 | ),
136 | ),
137 | Padding(
138 | padding: const EdgeInsets.all(8.0),
139 | child: Row(
140 | mainAxisAlignment: MainAxisAlignment.center,
141 | children: [
142 | Flexible(
143 | child: AutoSizeText(
144 | "\$$_budget",
145 | style: TextStyle(fontSize: 100),
146 | maxLines: 1,
147 | ),
148 | ),
149 | ],
150 | ),
151 | ),
152 | Row(
153 | children: [
154 | Expanded(
155 | child: Container(
156 | color: Colors.blue[900],
157 | child: Padding(
158 | padding: const EdgeInsets.symmetric(
159 | vertical: 10.0, horizontal: 20),
160 | child: Text(
161 | "\$${_budget * widget.trip.getTotalTripDays()} total",
162 | style: TextStyle(color: Colors.white, fontSize: 20),
163 | ),
164 | ),
165 | ),
166 | )
167 | ],
168 | )
169 | ],
170 | ),
171 | );
172 | }
173 |
174 | Widget notesCard(context) {
175 | return Hero(
176 | tag: "TripNotes-${widget.trip.title}",
177 | transitionOnUserGestures: true,
178 | child: Card(
179 | color: Colors.deepPurpleAccent,
180 | child: InkWell(
181 | child: Column(
182 | children: [
183 | Padding(
184 | padding: const EdgeInsets.only(top: 10.0, left: 10.0),
185 | child: Row(
186 | children: [
187 | Text("Trip Notes",
188 | style: TextStyle(fontSize: 24, color: Colors.white)),
189 | ],
190 | ),
191 | ),
192 | Padding(
193 | padding: const EdgeInsets.all(8.0),
194 | child: Row(
195 | children: setNoteText(),
196 | ),
197 | )
198 | ],
199 | ),
200 | onTap: () {
201 | Navigator.push(
202 | context,
203 | MaterialPageRoute(
204 | builder: (context) => EditNotesView(trip: widget.trip)));
205 | },
206 | ),
207 | ),
208 | );
209 | }
210 |
211 | List setNoteText() {
212 | if (widget.trip.notes == null) {
213 | return [
214 | Padding(
215 | padding: const EdgeInsets.only(right: 8.0),
216 | child: Icon(Icons.add_circle_outline, color: Colors.grey[300]),
217 | ),
218 | Text("Click To Add Notes", style: TextStyle(color: Colors.grey[300])),
219 | ];
220 | } else {
221 | return [
222 | Text(widget.trip.notes, style: TextStyle(color: Colors.grey[300]))
223 | ];
224 | }
225 | }
226 |
227 |
228 | void _tripEditModalBottomSheet(context) {
229 | showModalBottomSheet(
230 | context: context,
231 | builder: (BuildContext bc) {
232 | return Container(
233 | height: MediaQuery.of(context).size.height * .60,
234 | child: Padding(
235 | padding: const EdgeInsets.all(8.0),
236 | child: Column(
237 | children: [
238 | Row(
239 | children: [
240 | Text("Edit Trip"),
241 | Spacer(),
242 | IconButton(
243 | icon: Icon(
244 | Icons.cancel,
245 | color: Colors.orange,
246 | size: 25,
247 | ),
248 | onPressed: () {
249 | Navigator.of(context).pop();
250 | },
251 | )
252 | ],
253 | ),
254 | Row(
255 | children: [
256 | Text(
257 | widget.trip.title,
258 | style: TextStyle(fontSize: 30, color: Colors.green[900]),
259 | ),
260 | ],
261 | ),
262 | Row(
263 | children: [
264 | Expanded(
265 | child: MoneyTextField(
266 | controller: _budgetController,
267 | helperText: "Budget",
268 | ),
269 | )
270 | ],
271 | ),
272 | Row(
273 | mainAxisAlignment: MainAxisAlignment.center,
274 | children: [
275 | RaisedButton(
276 | child: Text('Submit'),
277 | color: Colors.deepPurple,
278 | textColor: Colors.white,
279 | onPressed: () async {
280 | widget.trip.budget = double.parse(_budgetController.text);
281 | setState(() {
282 | _budget = widget.trip.budget.floor();
283 | });
284 | await updateTrip(context);
285 | Navigator.of(context).pop();
286 | },
287 | )
288 | ],
289 | ),
290 | Row(
291 | mainAxisAlignment: MainAxisAlignment.center,
292 | children: [
293 | RaisedButton(
294 | child: Text('Delete'),
295 | color: Colors.red,
296 | textColor: Colors.white,
297 | onPressed: () async {
298 | await deleteTrip(context);
299 | Navigator.of(context).pushNamedAndRemoveUntil('/home', (Route route) => false);
300 | },
301 | )
302 | ],
303 | )
304 | ],
305 | ),
306 | ),
307 | );
308 | },
309 | );
310 | }
311 |
312 | Future updateTrip(context) async {
313 | var uid = await Provider.of(context).auth.getCurrentUID();
314 | final doc = FirebaseFirestore.instance
315 | .collection('userData')
316 | .doc(uid)
317 | .collection("trips")
318 | .doc(widget.trip.documentId);
319 |
320 | return await doc.set(widget.trip.toJson());
321 | }
322 |
323 | Future deleteTrip(context) async {
324 | var uid = await Provider.of(context).auth.getCurrentUID();
325 | final doc = FirebaseFirestore.instance
326 | .collection('userData')
327 | .doc(uid)
328 | .collection("trips")
329 | .doc(widget.trip.documentId);
330 |
331 | return await doc.delete();
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/lib/views/sign_up_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:apple_sign_in/apple_sign_in.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:travel_budget/services/auth_service.dart';
4 | import 'package:auto_size_text/auto_size_text.dart';
5 | import 'package:travel_budget/widgets/provider_widget.dart';
6 | import 'package:flutter_spinkit/flutter_spinkit.dart';
7 | import 'package:flutter_auth_buttons/flutter_auth_buttons.dart';
8 | import 'package:international_phone_input/international_phone_input.dart';
9 |
10 | // TODO move this to tone location
11 | final primaryColor = const Color(0xFF75A2EA);
12 |
13 | enum AuthFormType { signIn, signUp, reset, anonymous, convert, phone }
14 |
15 | class SignUpView extends StatefulWidget {
16 | final AuthFormType authFormType;
17 |
18 | SignUpView({Key key, @required this.authFormType}) : super(key: key);
19 |
20 | @override
21 | _SignUpViewState createState() =>
22 | _SignUpViewState(authFormType: this.authFormType);
23 | }
24 |
25 | class _SignUpViewState extends State {
26 | AuthFormType authFormType;
27 | bool _showAppleSignIn = false;
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 |
33 | _useAppleSignIn();
34 | }
35 |
36 | _useAppleSignIn() async {
37 | final isAvailable = await AppleSignIn.isAvailable();
38 | setState(() {
39 | _showAppleSignIn = isAvailable;
40 | });
41 | }
42 |
43 | _SignUpViewState({this.authFormType});
44 |
45 | final formKey = GlobalKey();
46 | String _email, _password, _name, _warning, _phone;
47 |
48 | void switchFormState(String state) {
49 | formKey.currentState.reset();
50 | if (state == "signUp") {
51 | setState(() {
52 | authFormType = AuthFormType.signUp;
53 | });
54 | } else if (state == 'home') {
55 | Navigator.of(context).pop();
56 | } else {
57 | setState(() {
58 | authFormType = AuthFormType.signIn;
59 | });
60 | }
61 | }
62 |
63 | bool validate() {
64 | final form = formKey.currentState;
65 | if (authFormType == AuthFormType.anonymous) {
66 | return true;
67 | }
68 | form.save();
69 | if (form.validate()) {
70 | form.save();
71 | return true;
72 | } else {
73 | return false;
74 | }
75 | }
76 |
77 | void submit() async {
78 | if (validate()) {
79 | try {
80 | final auth = Provider.of(context).auth;
81 | switch (authFormType) {
82 | case AuthFormType.signIn:
83 | await auth.signInWithEmailAndPassword(_email, _password);
84 | Navigator.of(context).pushReplacementNamed('/home');
85 | break;
86 | case AuthFormType.signUp:
87 | await auth.createUserWithEmailAndPassword(_email, _password, _name);
88 | Navigator.of(context).pushReplacementNamed('/home');
89 | break;
90 | case AuthFormType.reset:
91 | await auth.sendPasswordResetEmail(_email);
92 | setState(() {
93 | _warning = "A password reset link has been sent to $_email";
94 | authFormType = AuthFormType.signIn;
95 | });
96 | break;
97 | case AuthFormType.anonymous:
98 | await auth.singInAnonymously();
99 | Navigator.of(context).pushReplacementNamed('/home');
100 | break;
101 | case AuthFormType.convert:
102 | await auth.convertUserWithEmail(_email, _password, _name);
103 | Navigator.of(context).pop();
104 | break;
105 | case AuthFormType.phone:
106 | var result = await auth.createUserWithPhone(_phone, context);
107 | if (_phone == "" || result == "error") {
108 | setState(() {
109 | _warning = "Your phone number could not be validated";
110 | });
111 | }
112 | break;
113 | }
114 | } catch (e) {
115 | setState(() {
116 | _warning = e.message;
117 | });
118 | }
119 | }
120 | }
121 |
122 | @override
123 | Widget build(BuildContext context) {
124 | final _width = MediaQuery.of(context).size.width;
125 | final _height = MediaQuery.of(context).size.height;
126 |
127 | if (authFormType == AuthFormType.anonymous) {
128 | submit();
129 | return Scaffold(
130 | backgroundColor: primaryColor,
131 | body: SingleChildScrollView(
132 | child: Column(
133 | mainAxisAlignment: MainAxisAlignment.center,
134 | children: [
135 | SpinKitDoubleBounce(
136 | color: Colors.white,
137 | ),
138 | Text(
139 | "Loading",
140 | style: TextStyle(color: Colors.white),
141 | ),
142 | ],
143 | ),
144 | ));
145 | } else {
146 | return Scaffold(
147 | body: SingleChildScrollView(
148 | child: Container(
149 | color: primaryColor,
150 | height: _height,
151 | width: _width,
152 | child: SafeArea(
153 | child: Column(
154 | children: [
155 | SizedBox(height: _height * 0.025),
156 | showAlert(),
157 | SizedBox(height: _height * 0.025),
158 | buildHeaderText(),
159 | SizedBox(height: _height * 0.05),
160 | Padding(
161 | padding: const EdgeInsets.all(20.0),
162 | child: Form(
163 | key: formKey,
164 | child: Column(
165 | children: buildInputs() + buildButtons(),
166 | ),
167 | ),
168 | ),
169 | ],
170 | ),
171 | ),
172 | ),
173 | ),
174 | );
175 | }
176 | }
177 |
178 | Widget showAlert() {
179 | if (_warning != null) {
180 | return Container(
181 | color: Colors.amberAccent,
182 | width: double.infinity,
183 | padding: EdgeInsets.all(8.0),
184 | child: Row(
185 | children: [
186 | Padding(
187 | padding: const EdgeInsets.only(right: 8.0),
188 | child: Icon(Icons.error_outline),
189 | ),
190 | Expanded(
191 | child: AutoSizeText(
192 | _warning,
193 | maxLines: 3,
194 | ),
195 | ),
196 | Padding(
197 | padding: const EdgeInsets.only(left: 8.0),
198 | child: IconButton(
199 | icon: Icon(Icons.close),
200 | onPressed: () {
201 | setState(() {
202 | _warning = null;
203 | });
204 | },
205 | ),
206 | )
207 | ],
208 | ),
209 | );
210 | }
211 | return SizedBox(
212 | height: 0,
213 | );
214 | }
215 |
216 | AutoSizeText buildHeaderText() {
217 | String _headerText;
218 | if (authFormType == AuthFormType.signIn) {
219 | _headerText = "Sign In";
220 | } else if (authFormType == AuthFormType.reset) {
221 | _headerText = "Reset Password";
222 | } else if (authFormType == AuthFormType.phone) {
223 | _headerText = "Phone Sign In";
224 | } else {
225 | _headerText = "Create New Account";
226 | }
227 | return AutoSizeText(
228 | _headerText,
229 | maxLines: 1,
230 | textAlign: TextAlign.center,
231 | style: TextStyle(
232 | fontSize: 35,
233 | color: Colors.white,
234 | ),
235 | );
236 | }
237 |
238 | void onPhoneNumberChange(
239 | String number, String internationalizedPhoneNumber, String isoCode) {
240 | setState(() {
241 | _phone = internationalizedPhoneNumber;
242 | });
243 | }
244 |
245 | List buildInputs() {
246 | List textFields = [];
247 |
248 | // if were in the sign up state add name
249 | if ([AuthFormType.signUp, AuthFormType.convert].contains(authFormType)) {
250 | textFields.add(
251 | TextFormField(
252 | validator: NameValidator.validate,
253 | style: TextStyle(fontSize: 22.0),
254 | decoration: buildSignUpInputDecoration("Name"),
255 | onSaved: (value) => _name = value,
256 | ),
257 | );
258 | textFields.add(SizedBox(height: 20));
259 | }
260 |
261 | // add email & password
262 | if ([
263 | AuthFormType.signUp,
264 | AuthFormType.convert,
265 | AuthFormType.reset,
266 | AuthFormType.signIn
267 | ].contains(authFormType)) {
268 | textFields.add(
269 | TextFormField(
270 | validator: EmailValidator.validate,
271 | style: TextStyle(fontSize: 22.0),
272 | decoration: buildSignUpInputDecoration("Email"),
273 | onSaved: (value) => _email = value,
274 | ),
275 | );
276 | textFields.add(SizedBox(height: 20));
277 | }
278 |
279 | if (authFormType != AuthFormType.reset &&
280 | authFormType != AuthFormType.phone) {
281 | textFields.add(
282 | TextFormField(
283 | validator: PasswordValidator.validate,
284 | style: TextStyle(fontSize: 22.0),
285 | decoration: buildSignUpInputDecoration("Password"),
286 | obscureText: true,
287 | onSaved: (value) => _password = value,
288 | ),
289 | );
290 | textFields.add(SizedBox(height: 20));
291 | }
292 |
293 | if (authFormType == AuthFormType.phone) {
294 | textFields.add(
295 | InternationalPhoneInput(
296 | decoration: buildSignUpInputDecoration("Enter Phone Number"),
297 | onPhoneNumberChange: onPhoneNumberChange,
298 | initialPhoneNumber: _phone,
299 | initialSelection: 'US',
300 | showCountryCodes: true),
301 | );
302 | textFields.add(SizedBox(height: 20));
303 | }
304 |
305 | return textFields;
306 | }
307 |
308 | InputDecoration buildSignUpInputDecoration(String hint) {
309 | return InputDecoration(
310 | hintText: hint,
311 | filled: true,
312 | fillColor: Colors.white,
313 | focusColor: Colors.white,
314 | enabledBorder: OutlineInputBorder(
315 | borderSide: BorderSide(color: Colors.white, width: 0.0)),
316 | contentPadding:
317 | const EdgeInsets.only(left: 14.0, bottom: 10.0, top: 10.0),
318 | );
319 | }
320 |
321 | List buildButtons() {
322 | String _switchButtonText, _newFormState, _submitButtonText;
323 | bool _showForgotPassword = false;
324 | bool _showSocial = true;
325 |
326 | if (authFormType == AuthFormType.signIn) {
327 | _switchButtonText = "Create New Account";
328 | _newFormState = "signUp";
329 | _submitButtonText = "Sign In";
330 | _showForgotPassword = true;
331 | } else if (authFormType == AuthFormType.reset) {
332 | _switchButtonText = "Return to Sign In";
333 | _newFormState = "signIn";
334 | _submitButtonText = "Submit";
335 | _showSocial = false;
336 | } else if (authFormType == AuthFormType.convert) {
337 | _switchButtonText = "Cancel";
338 | _newFormState = "home";
339 | _submitButtonText = "Sign Up";
340 | } else if (authFormType == AuthFormType.phone) {
341 | _switchButtonText = "Cancel";
342 | _newFormState = "signIn";
343 | _submitButtonText = "Continue";
344 | _showSocial = false;
345 | } else {
346 | _switchButtonText = "Have an Account? Sign In";
347 | _newFormState = "signIn";
348 | _submitButtonText = "Sign Up";
349 | }
350 |
351 | return [
352 | Container(
353 | width: MediaQuery.of(context).size.width * 0.7,
354 | child: RaisedButton(
355 | shape:
356 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)),
357 | color: Colors.white,
358 | textColor: primaryColor,
359 | child: Padding(
360 | padding: const EdgeInsets.all(8.0),
361 | child: Text(
362 | _submitButtonText,
363 | style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.w300),
364 | ),
365 | ),
366 | onPressed: submit,
367 | ),
368 | ),
369 | showForgotPassword(_showForgotPassword),
370 | FlatButton(
371 | child: Text(
372 | _switchButtonText,
373 | style: TextStyle(color: Colors.white),
374 | ),
375 | onPressed: () {
376 | switchFormState(_newFormState);
377 | },
378 | ),
379 | buildSocialIcons(_showSocial),
380 | ];
381 | }
382 |
383 | Widget showForgotPassword(bool visible) {
384 | return Visibility(
385 | child: FlatButton(
386 | child: Text(
387 | "Forgot Password?",
388 | style: TextStyle(color: Colors.white),
389 | ),
390 | onPressed: () {
391 | setState(() {
392 | authFormType = AuthFormType.reset;
393 | });
394 | },
395 | ),
396 | visible: visible,
397 | );
398 | }
399 |
400 | Widget buildSocialIcons(bool visible) {
401 | final _auth = Provider.of(context).auth;
402 | return Visibility(
403 | child: Column(
404 | crossAxisAlignment: CrossAxisAlignment.stretch,
405 | children: [
406 | Divider(
407 | color: Colors.white,
408 | ),
409 | SizedBox(height: 10),
410 | buildAppleSignIn(_auth),
411 | SizedBox(height: 10),
412 | GoogleSignInButton(
413 | onPressed: () async {
414 | try {
415 | if (authFormType == AuthFormType.convert) {
416 | await _auth.convertWithGoogle();
417 | Navigator.of(context).pop();
418 | } else {
419 | await _auth.signInWithGoogle();
420 | Navigator.of(context).pushReplacementNamed('/home');
421 | }
422 | } catch (e) {
423 | setState(() {
424 | _warning = e.message;
425 | });
426 | }
427 | },
428 | ),
429 | RaisedButton(
430 | color: Colors.green,
431 | textColor: Colors.white,
432 | child: Row(
433 | children: [
434 | Icon(Icons.phone),
435 | Padding(
436 | padding: const EdgeInsets.only(
437 | left: 14.0, top: 10.0, bottom: 10.0),
438 | child: Text("Sign in with Phone",
439 | style: TextStyle(fontSize: 18)),
440 | )
441 | ],
442 | ),
443 | onPressed: () {
444 | setState(() {
445 | authFormType = AuthFormType.phone;
446 | });
447 | },
448 | ),
449 | ],
450 | ),
451 | visible: visible,
452 | );
453 | }
454 |
455 | Widget buildAppleSignIn(_auth) {
456 | if (authFormType != AuthFormType.convert && _showAppleSignIn == true) {
457 | return AppleSignInButton(
458 | onPressed: () async {
459 | await _auth.signInWithApple();
460 | Navigator.of(context).pushReplacementNamed('/home');
461 | },
462 | style: ButtonStyle.black,
463 | );
464 | } else {
465 | return Container();
466 | }
467 | }
468 | }
469 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | apple_sign_in:
5 | dependency: "direct main"
6 | description:
7 | name: apple_sign_in
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "0.1.0"
11 | archive:
12 | dependency: transitive
13 | description:
14 | name: archive
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "2.0.13"
18 | args:
19 | dependency: transitive
20 | description:
21 | name: args
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "1.6.0"
25 | async:
26 | dependency: transitive
27 | description:
28 | name: async
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "2.4.2"
32 | auto_size_text:
33 | dependency: "direct main"
34 | description:
35 | name: auto_size_text
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "2.1.0"
39 | boolean_selector:
40 | dependency: transitive
41 | description:
42 | name: boolean_selector
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "2.0.0"
46 | built_collection:
47 | dependency: transitive
48 | description:
49 | name: built_collection
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "4.3.2"
53 | built_value:
54 | dependency: transitive
55 | description:
56 | name: built_value
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "7.1.0"
60 | characters:
61 | dependency: transitive
62 | description:
63 | name: characters
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "1.0.0"
67 | charcode:
68 | dependency: transitive
69 | description:
70 | name: charcode
71 | url: "https://pub.dartlang.org"
72 | source: hosted
73 | version: "1.1.3"
74 | clock:
75 | dependency: transitive
76 | description:
77 | name: clock
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "1.0.1"
81 | cloud_firestore:
82 | dependency: "direct main"
83 | description:
84 | name: cloud_firestore
85 | url: "https://pub.dartlang.org"
86 | source: hosted
87 | version: "0.14.4"
88 | cloud_firestore_platform_interface:
89 | dependency: transitive
90 | description:
91 | name: cloud_firestore_platform_interface
92 | url: "https://pub.dartlang.org"
93 | source: hosted
94 | version: "2.2.1"
95 | cloud_firestore_web:
96 | dependency: transitive
97 | description:
98 | name: cloud_firestore_web
99 | url: "https://pub.dartlang.org"
100 | source: hosted
101 | version: "0.2.1+2"
102 | collection:
103 | dependency: transitive
104 | description:
105 | name: collection
106 | url: "https://pub.dartlang.org"
107 | source: hosted
108 | version: "1.14.13"
109 | convert:
110 | dependency: transitive
111 | description:
112 | name: convert
113 | url: "https://pub.dartlang.org"
114 | source: hosted
115 | version: "2.1.1"
116 | crypto:
117 | dependency: transitive
118 | description:
119 | name: crypto
120 | url: "https://pub.dartlang.org"
121 | source: hosted
122 | version: "2.1.5"
123 | cupertino_icons:
124 | dependency: "direct main"
125 | description:
126 | name: cupertino_icons
127 | url: "https://pub.dartlang.org"
128 | source: hosted
129 | version: "0.1.3"
130 | date_range_picker:
131 | dependency: "direct main"
132 | description:
133 | name: date_range_picker
134 | url: "https://pub.dartlang.org"
135 | source: hosted
136 | version: "1.0.6"
137 | device_info:
138 | dependency: "direct main"
139 | description:
140 | name: device_info
141 | url: "https://pub.dartlang.org"
142 | source: hosted
143 | version: "0.4.2+7"
144 | device_info_platform_interface:
145 | dependency: transitive
146 | description:
147 | name: device_info_platform_interface
148 | url: "https://pub.dartlang.org"
149 | source: hosted
150 | version: "1.0.1"
151 | dio:
152 | dependency: "direct main"
153 | description:
154 | name: dio
155 | url: "https://pub.dartlang.org"
156 | source: hosted
157 | version: "3.0.10"
158 | fake_async:
159 | dependency: transitive
160 | description:
161 | name: fake_async
162 | url: "https://pub.dartlang.org"
163 | source: hosted
164 | version: "1.1.0"
165 | file:
166 | dependency: transitive
167 | description:
168 | name: file
169 | url: "https://pub.dartlang.org"
170 | source: hosted
171 | version: "5.2.1"
172 | firebase_admob:
173 | dependency: "direct main"
174 | description:
175 | name: firebase_admob
176 | url: "https://pub.dartlang.org"
177 | source: hosted
178 | version: "0.10.0-dev.1"
179 | firebase_auth:
180 | dependency: "direct main"
181 | description:
182 | name: firebase_auth
183 | url: "https://pub.dartlang.org"
184 | source: hosted
185 | version: "0.18.4+1"
186 | firebase_auth_platform_interface:
187 | dependency: transitive
188 | description:
189 | name: firebase_auth_platform_interface
190 | url: "https://pub.dartlang.org"
191 | source: hosted
192 | version: "2.1.4"
193 | firebase_auth_web:
194 | dependency: transitive
195 | description:
196 | name: firebase_auth_web
197 | url: "https://pub.dartlang.org"
198 | source: hosted
199 | version: "0.3.2+3"
200 | firebase_core:
201 | dependency: "direct main"
202 | description:
203 | name: firebase_core
204 | url: "https://pub.dartlang.org"
205 | source: hosted
206 | version: "0.5.3"
207 | firebase_core_platform_interface:
208 | dependency: transitive
209 | description:
210 | name: firebase_core_platform_interface
211 | url: "https://pub.dartlang.org"
212 | source: hosted
213 | version: "2.1.0"
214 | firebase_core_web:
215 | dependency: transitive
216 | description:
217 | name: firebase_core_web
218 | url: "https://pub.dartlang.org"
219 | source: hosted
220 | version: "0.2.1+1"
221 | firebase_crashlytics:
222 | dependency: "direct main"
223 | description:
224 | name: firebase_crashlytics
225 | url: "https://pub.dartlang.org"
226 | source: hosted
227 | version: "0.2.4"
228 | firebase_crashlytics_platform_interface:
229 | dependency: transitive
230 | description:
231 | name: firebase_crashlytics_platform_interface
232 | url: "https://pub.dartlang.org"
233 | source: hosted
234 | version: "1.1.4"
235 | fixnum:
236 | dependency: transitive
237 | description:
238 | name: fixnum
239 | url: "https://pub.dartlang.org"
240 | source: hosted
241 | version: "0.10.11"
242 | flare_dart:
243 | dependency: transitive
244 | description:
245 | name: flare_dart
246 | url: "https://pub.dartlang.org"
247 | source: hosted
248 | version: "2.3.4"
249 | flare_flutter:
250 | dependency: "direct main"
251 | description:
252 | name: flare_flutter
253 | url: "https://pub.dartlang.org"
254 | source: hosted
255 | version: "2.0.6"
256 | flutter:
257 | dependency: "direct main"
258 | description: flutter
259 | source: sdk
260 | version: "0.0.0"
261 | flutter_auth_buttons:
262 | dependency: "direct main"
263 | description:
264 | name: flutter_auth_buttons
265 | url: "https://pub.dartlang.org"
266 | source: hosted
267 | version: "0.5.0"
268 | flutter_launcher_icons:
269 | dependency: "direct main"
270 | description:
271 | name: flutter_launcher_icons
272 | url: "https://pub.dartlang.org"
273 | source: hosted
274 | version: "0.7.5"
275 | flutter_spinkit:
276 | dependency: "direct main"
277 | description:
278 | name: flutter_spinkit
279 | url: "https://pub.dartlang.org"
280 | source: hosted
281 | version: "3.1.0"
282 | flutter_test:
283 | dependency: "direct dev"
284 | description: flutter
285 | source: sdk
286 | version: "0.0.0"
287 | flutter_web_plugins:
288 | dependency: transitive
289 | description: flutter
290 | source: sdk
291 | version: "0.0.0"
292 | google_fonts:
293 | dependency: "direct main"
294 | description:
295 | name: google_fonts
296 | url: "https://pub.dartlang.org"
297 | source: hosted
298 | version: "0.3.10"
299 | google_sign_in:
300 | dependency: "direct main"
301 | description:
302 | name: google_sign_in
303 | url: "https://pub.dartlang.org"
304 | source: hosted
305 | version: "4.5.3"
306 | google_sign_in_platform_interface:
307 | dependency: transitive
308 | description:
309 | name: google_sign_in_platform_interface
310 | url: "https://pub.dartlang.org"
311 | source: hosted
312 | version: "1.1.2"
313 | google_sign_in_web:
314 | dependency: transitive
315 | description:
316 | name: google_sign_in_web
317 | url: "https://pub.dartlang.org"
318 | source: hosted
319 | version: "0.9.1+1"
320 | http:
321 | dependency: transitive
322 | description:
323 | name: http
324 | url: "https://pub.dartlang.org"
325 | source: hosted
326 | version: "0.12.2"
327 | http_parser:
328 | dependency: transitive
329 | description:
330 | name: http_parser
331 | url: "https://pub.dartlang.org"
332 | source: hosted
333 | version: "3.1.4"
334 | image:
335 | dependency: transitive
336 | description:
337 | name: image
338 | url: "https://pub.dartlang.org"
339 | source: hosted
340 | version: "2.1.14"
341 | international_phone_input:
342 | dependency: "direct main"
343 | description:
344 | name: international_phone_input
345 | url: "https://pub.dartlang.org"
346 | source: hosted
347 | version: "1.0.4"
348 | intl:
349 | dependency: "direct main"
350 | description:
351 | name: intl
352 | url: "https://pub.dartlang.org"
353 | source: hosted
354 | version: "0.16.1"
355 | js:
356 | dependency: transitive
357 | description:
358 | name: js
359 | url: "https://pub.dartlang.org"
360 | source: hosted
361 | version: "0.6.2"
362 | libphonenumber:
363 | dependency: transitive
364 | description:
365 | name: libphonenumber
366 | url: "https://pub.dartlang.org"
367 | source: hosted
368 | version: "1.0.1"
369 | matcher:
370 | dependency: transitive
371 | description:
372 | name: matcher
373 | url: "https://pub.dartlang.org"
374 | source: hosted
375 | version: "0.12.8"
376 | meta:
377 | dependency: transitive
378 | description:
379 | name: meta
380 | url: "https://pub.dartlang.org"
381 | source: hosted
382 | version: "1.1.8"
383 | path:
384 | dependency: transitive
385 | description:
386 | name: path
387 | url: "https://pub.dartlang.org"
388 | source: hosted
389 | version: "1.7.0"
390 | path_provider:
391 | dependency: transitive
392 | description:
393 | name: path_provider
394 | url: "https://pub.dartlang.org"
395 | source: hosted
396 | version: "1.6.14"
397 | path_provider_linux:
398 | dependency: transitive
399 | description:
400 | name: path_provider_linux
401 | url: "https://pub.dartlang.org"
402 | source: hosted
403 | version: "0.0.1+2"
404 | path_provider_macos:
405 | dependency: transitive
406 | description:
407 | name: path_provider_macos
408 | url: "https://pub.dartlang.org"
409 | source: hosted
410 | version: "0.0.4+3"
411 | path_provider_platform_interface:
412 | dependency: transitive
413 | description:
414 | name: path_provider_platform_interface
415 | url: "https://pub.dartlang.org"
416 | source: hosted
417 | version: "1.0.3"
418 | pedantic:
419 | dependency: transitive
420 | description:
421 | name: pedantic
422 | url: "https://pub.dartlang.org"
423 | source: hosted
424 | version: "1.9.0"
425 | petitparser:
426 | dependency: transitive
427 | description:
428 | name: petitparser
429 | url: "https://pub.dartlang.org"
430 | source: hosted
431 | version: "3.0.4"
432 | platform:
433 | dependency: transitive
434 | description:
435 | name: platform
436 | url: "https://pub.dartlang.org"
437 | source: hosted
438 | version: "2.2.1"
439 | plugin_platform_interface:
440 | dependency: transitive
441 | description:
442 | name: plugin_platform_interface
443 | url: "https://pub.dartlang.org"
444 | source: hosted
445 | version: "1.0.2"
446 | process:
447 | dependency: transitive
448 | description:
449 | name: process
450 | url: "https://pub.dartlang.org"
451 | source: hosted
452 | version: "3.0.13"
453 | quiver:
454 | dependency: transitive
455 | description:
456 | name: quiver
457 | url: "https://pub.dartlang.org"
458 | source: hosted
459 | version: "2.1.3"
460 | sky_engine:
461 | dependency: transitive
462 | description: flutter
463 | source: sdk
464 | version: "0.0.99"
465 | source_span:
466 | dependency: transitive
467 | description:
468 | name: source_span
469 | url: "https://pub.dartlang.org"
470 | source: hosted
471 | version: "1.7.0"
472 | stack_trace:
473 | dependency: transitive
474 | description:
475 | name: stack_trace
476 | url: "https://pub.dartlang.org"
477 | source: hosted
478 | version: "1.9.5"
479 | stream_channel:
480 | dependency: transitive
481 | description:
482 | name: stream_channel
483 | url: "https://pub.dartlang.org"
484 | source: hosted
485 | version: "2.0.0"
486 | string_scanner:
487 | dependency: transitive
488 | description:
489 | name: string_scanner
490 | url: "https://pub.dartlang.org"
491 | source: hosted
492 | version: "1.0.5"
493 | term_glyph:
494 | dependency: transitive
495 | description:
496 | name: term_glyph
497 | url: "https://pub.dartlang.org"
498 | source: hosted
499 | version: "1.1.0"
500 | test_api:
501 | dependency: transitive
502 | description:
503 | name: test_api
504 | url: "https://pub.dartlang.org"
505 | source: hosted
506 | version: "0.2.17"
507 | typed_data:
508 | dependency: transitive
509 | description:
510 | name: typed_data
511 | url: "https://pub.dartlang.org"
512 | source: hosted
513 | version: "1.2.0"
514 | uuid:
515 | dependency: "direct main"
516 | description:
517 | name: uuid
518 | url: "https://pub.dartlang.org"
519 | source: hosted
520 | version: "2.0.1"
521 | vector_math:
522 | dependency: transitive
523 | description:
524 | name: vector_math
525 | url: "https://pub.dartlang.org"
526 | source: hosted
527 | version: "2.0.8"
528 | xdg_directories:
529 | dependency: transitive
530 | description:
531 | name: xdg_directories
532 | url: "https://pub.dartlang.org"
533 | source: hosted
534 | version: "0.1.0"
535 | xml:
536 | dependency: transitive
537 | description:
538 | name: xml
539 | url: "https://pub.dartlang.org"
540 | source: hosted
541 | version: "4.2.0"
542 | yaml:
543 | dependency: transitive
544 | description:
545 | name: yaml
546 | url: "https://pub.dartlang.org"
547 | source: hosted
548 | version: "2.2.1"
549 | sdks:
550 | dart: ">=2.9.0-14.0.dev <3.0.0"
551 | flutter: ">=1.12.13+hotfix.5 <2.0.0"
552 |
--------------------------------------------------------------------------------