├── .env.dist ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android ├── .gitignore ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── app │ ├── .classpath │ ├── .project │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── tfg │ │ │ │ └── daruma │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── assets ├── daruma-logo-black.png ├── daruma-logo.png ├── facebook_logo.png └── google_logo.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── main.dart ├── model │ ├── balance.dart │ ├── bill.dart │ ├── currency-list.dart │ ├── group.dart │ ├── member.dart │ ├── owner.dart │ ├── participant.dart │ ├── recurring-bill.dart │ ├── transaction.dart │ └── user.dart ├── redux │ ├── actions.dart │ ├── index.dart │ ├── middleware.dart │ ├── reducer.dart │ ├── state.dart │ └── store.dart ├── services │ ├── balance-calculator │ │ └── balance-calculator.dart │ ├── bloc │ │ ├── balance.bloc.dart │ │ ├── bill.bloc.dart │ │ ├── currency-list.bloc.dart │ │ ├── group.bloc.dart │ │ ├── member.bloc.dart │ │ ├── transaction.bloc.dart │ │ └── user.bloc.dart │ ├── dynamic_link │ │ └── dynamic_links.dart │ ├── networking │ │ ├── api-provider.dart │ │ ├── custom-exception.dart │ │ ├── index.dart │ │ └── response.dart │ └── repository │ │ ├── balance.repository.dart │ │ ├── bill.repository.dart │ │ ├── currency-list.repository.dart │ │ ├── group.repository.dart │ │ ├── member.repository.dart │ │ ├── transaction.repository.dart │ │ └── user.repository.dart ├── ui │ ├── pages │ │ ├── add-members.page.dart │ │ ├── bills-history.page.dart │ │ ├── create-bill.page.dart │ │ ├── create-group.page.dart │ │ ├── detail-bill.page.dart │ │ ├── detail-recurring-bill.page.dart │ │ ├── edit-bill.page.dart │ │ ├── edit-group.page.dart │ │ ├── edit-profile.page.dart │ │ ├── group-balance.page.dart │ │ ├── group-bills.page.dart │ │ ├── group.page.dart │ │ ├── login.page.dart │ │ ├── recurring-bills.page.dart │ │ ├── transfer-money.page.dart │ │ └── welcome.page.dart │ └── widget │ │ ├── add-member-dialog.widget.dart │ │ ├── balance-list.widget.dart │ │ ├── bills-list.widget.dart │ │ ├── bottom-navigation-bar.widget.dart │ │ ├── create-bill-floating-button.widget.dart │ │ ├── currency-button.widget.dart │ │ ├── currency-list.widget.dart │ │ ├── delete-bill-dialog.widget.dart │ │ ├── delete-group-dialog.widget.dart │ │ ├── delete-member-dialog.widget.dart │ │ ├── delete-recurring-bill-dialog.widget.dart │ │ ├── detail-bill-app-bar-title.widget.dart │ │ ├── detail-bill-flexible-app-bar.dart │ │ ├── edit-bill-dialog.widget.dart │ │ ├── edit-group-dialog.widget.dart │ │ ├── edit-profile-dialog.widget.dart │ │ ├── edit-recurring-bill-dialog.widget.dart │ │ ├── group-button.widget.dart │ │ ├── groups-list.widget.dart │ │ ├── index.dart │ │ ├── members-button.widget.dart │ │ ├── members-list.widget.dart │ │ ├── number-form-field.widget.dart │ │ ├── oauth-login-button.widget.dart │ │ ├── post-bill-dialog.widget.dart │ │ ├── post-group-dialog.widget.dart │ │ ├── post-recurring-bill-dialog.widget.dart │ │ ├── post-transfer-dialog.widget.dart │ │ ├── recurring-bills-list.widget.dart │ │ ├── select-member-in-group-dialog.widget.dart │ │ ├── set-userid-to-member-dialog.dart │ │ ├── sign-out-button.widget.dart │ │ └── text-form-field.widget.dart └── util │ ├── colors.dart │ ├── csv.dart │ ├── keys.dart │ ├── output-file.dart │ ├── routes.dart │ └── url.dart ├── pubspec.lock ├── pubspec.yaml ├── screenshots ├── balances.png ├── detail-bill.png ├── groups.png ├── history.png └── new-bill.png └── test └── widget_test.dart /.env.dist: -------------------------------------------------------------------------------- 1 | HOST= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | # IDE - VSCode 40 | .vscode/ 41 | !.vscode/settings.json 42 | !.vscode/tasks.json 43 | !.vscode/launch.json 44 | !.vscode/extensions.json 45 | .env 46 | 47 | 48 | # Google service 49 | android/app/google-services.json -------------------------------------------------------------------------------- /.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: 0b8abb4724aa590dd0f429683339b1e045a1594d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Shared expenses management system. 2 | Copyright (C) 2020 Adrián López Guerrero 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU Affero General Public License as published 6 | by the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | 14 | You should have received a copy of the GNU Affero General Public License 15 | along with this program. If not, see . 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | Daruma Logo 4 |
5 | 🎎 Daruma - Frontend 🎎 6 |
7 |

8 | 9 | 10 |

Shared expenses management system built on top of NestJS and Flutter.

11 | 12 |

13 | 14 | 15 | 16 | 17 | 18 |

19 | 20 | ## Table of Contents 21 | * [About the Project](#about-the-project) 22 | * [Key Features](#key-features) 23 | * [Install, build and run!](#download) 24 | * [Built With](#build) 25 | * [Related](#related) 26 | * [License](#license) 27 | * [Contact](#contact) 28 | 29 | ## About the project 30 | This repository holds my final year project during my time at the University of Córdoba titled "Daruma, shared expenses management system" built with NestJS framework. 31 | 32 | The purpose of this project is to learn new technologies like NestJS and Flutter and apply concepts about Software Desing like Domain-Driven-Desing, CQRS, Event Sourcing, Clean code, unit, integration and End-to-End testing, etc. 33 | 34 | Daruma allows users to easily manage shared expenses made in a group, including the management of recurring expenses like Netflix, HBO, etc... 35 | 36 | 37 | 38 | ## Key Features 39 | 40 | * **State management**: Redux, BloC, repository patterns . 41 | * **Backend connection**: API provider pattern. 42 | * **Invitation to group member**: Firebase Dynamic Links. 43 | 44 | ## Install, build and run! 45 | 46 | > **DISCLAIMER:** It is necessary to run backend server in order to use mobile app. For that, it is necessary to download, build and run "Daruma Backend", located in [this repository](https://github.com/AdrianLopezGue/daruma-backend) 47 | 48 | After running backend server, clone and run this application, you'll need [Git](https://git-scm.com) installed on your computer. From your command line: 49 | 50 | ```bash 51 | # Clone this repository 52 | $ git clone https://github.com/AdrianLopezGue/daruma-frontend 53 | 54 | # Go into the repository 55 | $ cd daruma-frontend 56 | ``` 57 | 58 | Download Android Studio or Visual Studio with Flutter editor plugins. After that, open the project and install dependencies from pubspec.yaml by running the following command: 59 | ```bash 60 | $ flutter packages get 61 | ``` 62 | 63 | ## Built With 64 | 65 | This software uses the following packages: 66 | 67 | - [Flutter](https://flutter.dev/) 68 | - [Dart](https://dart.dev/) 69 | - [Visual Studio Code](https://code.visualstudio.com/) 70 | 71 | ## Related 72 | 73 | [Daruma - Backend](https://github.com/AdrianLopezGue/daruma-backend) - Backend part of Daruma. 74 | 75 | ## License 76 | 77 | [GNU Affero General Public License v3 (AGPL)](https://www.gnu.org/licenses/agpl-3.0.en.html) 78 | 79 | ## Contact 80 | 81 | > GitHub - [@AdrianLopezGue](https://github.com/AdrianLopezGue) 82 | > LinkedIn - [Adrián López Guerrero](https://www.linkedin.com/in/adrianlopezgue/) 83 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=/usr/lib/jvm/java-11-openjdk-amd64 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | apply from: project(':flutter_config').projectDir.getPath() + "/dotenv.gradle" 28 | 29 | android { 30 | compileSdkVersion 28 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | 36 | lintOptions { 37 | disable 'InvalidPackage' 38 | } 39 | 40 | defaultConfig { 41 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 42 | applicationId "com.tfg.daruma" 43 | minSdkVersion 19 44 | targetSdkVersion 28 45 | versionCode flutterVersionCode.toInteger() 46 | versionName flutterVersionName 47 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 48 | multiDexEnabled true 49 | } 50 | 51 | buildTypes { 52 | release { 53 | // TODO: Add your own signing config for the release build. 54 | // Signing with the debug keys for now, so `flutter run --release` works. 55 | signingConfig signingConfigs.debug 56 | } 57 | } 58 | } 59 | 60 | flutter { 61 | source '../..' 62 | } 63 | 64 | apply plugin: 'com.google.gms.google-services' 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | testImplementation 'junit:junit:4.12' 69 | androidTestImplementation 'androidx.test:runner:1.1.1' 70 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 71 | implementation 'com.google.firebase:firebase-dynamic-links:19.0.0' 72 | implementation 'com.google.firebase:firebase-analytics:17.2.0' 73 | } 74 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 57 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/tfg/daruma/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tfg.daruma 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | daruma 4 | 5 | 6 | 2676396486015968 7 | fb2676396486015968 8 | 9 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.6.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.3' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Mar 06 17:38:03 CET 2020 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.4-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/daruma-logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/assets/daruma-logo-black.png -------------------------------------------------------------------------------- /assets/daruma-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/assets/daruma-logo.png -------------------------------------------------------------------------------- /assets/facebook_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/assets/facebook_logo.png -------------------------------------------------------------------------------- /assets/google_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/assets/google_logo.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "Icon-App-20x20@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "Icon-App-20x20@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "Icon-App-29x29@1x.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "Icon-App-29x29@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "29x29", 29 | "idiom": "iphone", 30 | "filename": "Icon-App-29x29@3x.png", 31 | "scale": "3x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "Icon-App-40x40@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "40x40", 41 | "idiom": "iphone", 42 | "filename": "Icon-App-40x40@3x.png", 43 | "scale": "3x" 44 | }, 45 | { 46 | "size": "60x60", 47 | "idiom": "iphone", 48 | "filename": "Icon-App-60x60@2x.png", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "60x60", 53 | "idiom": "iphone", 54 | "filename": "Icon-App-60x60@3x.png", 55 | "scale": "3x" 56 | }, 57 | { 58 | "size": "20x20", 59 | "idiom": "ipad", 60 | "filename": "Icon-App-20x20@1x.png", 61 | "scale": "1x" 62 | }, 63 | { 64 | "size": "20x20", 65 | "idiom": "ipad", 66 | "filename": "Icon-App-20x20@2x.png", 67 | "scale": "2x" 68 | }, 69 | { 70 | "size": "29x29", 71 | "idiom": "ipad", 72 | "filename": "Icon-App-29x29@1x.png", 73 | "scale": "1x" 74 | }, 75 | { 76 | "size": "29x29", 77 | "idiom": "ipad", 78 | "filename": "Icon-App-29x29@2x.png", 79 | "scale": "2x" 80 | }, 81 | { 82 | "size": "40x40", 83 | "idiom": "ipad", 84 | "filename": "Icon-App-40x40@1x.png", 85 | "scale": "1x" 86 | }, 87 | { 88 | "size": "40x40", 89 | "idiom": "ipad", 90 | "filename": "Icon-App-40x40@2x.png", 91 | "scale": "2x" 92 | }, 93 | { 94 | "size": "76x76", 95 | "idiom": "ipad", 96 | "filename": "Icon-App-76x76@1x.png", 97 | "scale": "1x" 98 | }, 99 | { 100 | "size": "76x76", 101 | "idiom": "ipad", 102 | "filename": "Icon-App-76x76@2x.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "83.5x83.5", 107 | "idiom": "ipad", 108 | "filename": "Icon-App-83.5x83.5@2x.png", 109 | "scale": "2x" 110 | }, 111 | { 112 | "size": "1024x1024", 113 | "idiom": "ios-marketing", 114 | "filename": "Icon-App-1024x1024@1x.png", 115 | "scale": "1x" 116 | } 117 | ], 118 | "info": { 119 | "version": 1, 120 | "author": "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/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/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "LaunchImage.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "LaunchImage@2x.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "LaunchImage@3x.png", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "version": 1, 21 | "author": "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. 6 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | daruma 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/redux/index.dart'; 2 | import 'package:daruma/redux/store.dart'; 3 | import 'package:daruma/ui/pages/group.page.dart'; 4 | import 'package:daruma/util/keys.dart'; 5 | import 'package:daruma/util/routes.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'package:daruma/ui/pages/login.page.dart'; 9 | import 'package:flutter_redux/flutter_redux.dart'; 10 | import 'package:redux/redux.dart'; 11 | import 'package:flutter_config/flutter_config.dart'; 12 | void main() async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | 15 | await FlutterConfig.loadEnvVariables(); 16 | runApp(MyApp()); 17 | } 18 | 19 | class MyApp extends StatelessWidget { 20 | final Store store = createStore(); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return StoreProvider( 25 | store: this.store, 26 | child: MaterialApp( 27 | debugShowCheckedModeBanner: false, 28 | navigatorKey: Keys.navKey, 29 | routes: { 30 | Routes.groupPage: (context) { 31 | return GroupPage(); 32 | } 33 | }, 34 | title: 'Daruma Login', 35 | theme: ThemeData( 36 | primarySwatch: Colors.red, 37 | ), 38 | home: LoginPage(), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/model/balance.dart: -------------------------------------------------------------------------------- 1 | class Balance { 2 | String memberId; 3 | int money; 4 | 5 | Balance(this.memberId, this.money); 6 | 7 | Balance.fromJson(Map json) { 8 | this.memberId = json['_id']; 9 | this.money = json['money']; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/model/bill.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/participant.dart'; 2 | 3 | class Bill { 4 | String billId; 5 | String groupId; 6 | String name; 7 | DateTime date; 8 | int money; 9 | String currencyCode; 10 | List payers; 11 | List debtors; 12 | String creatorId; 13 | 14 | Bill( 15 | {this.billId, 16 | this.groupId, 17 | this.name, 18 | this.date, 19 | this.money, 20 | this.currencyCode, 21 | this.payers, 22 | this.debtors, 23 | this.creatorId}); 24 | 25 | factory Bill.initial() { 26 | return new Bill( 27 | billId: '', 28 | groupId: '', 29 | name: '', 30 | date: DateTime.now(), 31 | money: 0, 32 | currencyCode: 'EUR', 33 | payers: [], 34 | debtors: [], 35 | creatorId: '', 36 | ); 37 | } 38 | 39 | Bill copyWith( 40 | {String billId, 41 | String groupId, 42 | String name, 43 | DateTime date, 44 | int money, 45 | String currencyCode, 46 | List payers, 47 | List debtors, 48 | String creatorId}) { 49 | return Bill( 50 | billId: billId ?? this.billId, 51 | groupId: groupId ?? this.groupId, 52 | name: name ?? this.name, 53 | date: date ?? this.date, 54 | money: money ?? this.money, 55 | currencyCode: currencyCode ?? this.currencyCode, 56 | payers: payers ?? this.payers, 57 | debtors: debtors ?? this.debtors, 58 | creatorId: creatorId ?? this.creatorId); 59 | } 60 | 61 | Map toJson() { 62 | List payers = this.payers != null 63 | ? this.payers.map((payer) => payer.toJson()).toList() 64 | : null; 65 | 66 | List finalDebtors = 67 | this.debtors.where((debtor) => debtor.money != -1).toList(); 68 | 69 | List debtors = finalDebtors != null 70 | ? finalDebtors.map((debtor) => debtor.toJson()).toList() 71 | : null; 72 | 73 | var json = { 74 | '_id': this.billId, 75 | 'groupId': this.groupId, 76 | 'name': this.name, 77 | 'date': this.date.toIso8601String(), 78 | 'money': this.money, 79 | 'currencyCode': this.currencyCode, 80 | 'payers': payers, 81 | 'debtors': debtors, 82 | 'creatorId': this.creatorId, 83 | }; 84 | 85 | return json; 86 | } 87 | 88 | Bill.fromJson(Map json) { 89 | this.billId = json['_id']; 90 | this.groupId = json['groupId']; 91 | this.name = json['name']; 92 | this.date = DateTime.parse(json['date']); 93 | this.money = json['money']; 94 | this.currencyCode = json['currencyCode']; 95 | this.payers = _parseParticipants(json['payers']); 96 | this.debtors = _parseParticipants(json['debtors']); 97 | this.creatorId = json['creatorId']; 98 | } 99 | 100 | List _parseParticipants(List participants) { 101 | var list = participants; 102 | List billParticipants = 103 | list.map((participant) => Participant.fromJson(participant)).toList(); 104 | 105 | return billParticipants; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/model/currency-list.dart: -------------------------------------------------------------------------------- 1 | class CurrencyList { 2 | final List rates; 3 | 4 | CurrencyList([this.rates]); 5 | 6 | factory CurrencyList.fromJson(Map json) { 7 | return CurrencyList(_parseRates(json)); 8 | } 9 | } 10 | 11 | class Currency { 12 | String code; 13 | String name; 14 | 15 | Currency(this.code, this.name); 16 | } 17 | 18 | List _parseRates(Map json) { 19 | return json.keys.map((code) => Currency(code, json[code])).toList(); 20 | } 21 | -------------------------------------------------------------------------------- /lib/model/group.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/member.dart'; 2 | import 'package:daruma/model/owner.dart'; 3 | 4 | class Group { 5 | String groupId; 6 | String name; 7 | String currencyCode; 8 | Owner owner; 9 | List members; 10 | 11 | Group({this.groupId, this.name, this.currencyCode, this.owner, this.members}); 12 | 13 | Group copyWith( 14 | {String id, 15 | String name, 16 | String currencyCode, 17 | String ownerId, 18 | List members}) { 19 | return Group( 20 | groupId: id ?? this.groupId, 21 | name: name ?? this.name, 22 | currencyCode: currencyCode ?? this.currencyCode, 23 | owner: owner ?? this.owner, 24 | members: members ?? this.members); 25 | } 26 | 27 | Map toJson() { 28 | List members = this.members != null 29 | ? this.members.map((member) => member.toJson()).toList() 30 | : null; 31 | 32 | Map owner = this.owner != null ? this.owner.toJson() : null; 33 | 34 | return { 35 | '_id': this.groupId, 36 | 'name': this.name, 37 | 'currencyCode': this.currencyCode, 38 | 'owner': owner, 39 | 'members': members 40 | }; 41 | } 42 | 43 | Group.fromJson(Map json) { 44 | this.groupId = json['_id']; 45 | this.name = json['name']; 46 | this.currencyCode = json['currencyCode']; 47 | 48 | Owner owner = new Owner(); 49 | owner.ownerId = json['ownerId']; 50 | owner.name = ''; 51 | this.owner = owner; 52 | this.members = []; 53 | } 54 | 55 | String getMembersAsString() { 56 | String result = ""; 57 | int index = 0; 58 | 59 | for (int i = 0; i < this.members.length - 1; i++) { 60 | index += this.members[i].name.length; 61 | 62 | if(index <= 40){ 63 | result += this.members[i].name + ", "; 64 | } 65 | } 66 | 67 | index += this.members.last.name.length; 68 | 69 | 70 | if(index <= 40){ 71 | result += this.members.last.name; 72 | } 73 | else{ 74 | result = result.substring(0, result.length-2); 75 | result += '...'; 76 | } 77 | 78 | return result; 79 | } 80 | 81 | String getMemberNameById(String memberId) { 82 | var selectedMember = 83 | this.members.singleWhere((member) => member.memberId == memberId); 84 | return selectedMember.name; 85 | } 86 | 87 | String getMemberIdByName(String name) { 88 | var selectedMember = 89 | this.members.singleWhere((member) => member.name == name); 90 | return selectedMember.memberId; 91 | } 92 | 93 | String getUserIdByMemberId(String memberId) { 94 | var selectedMember = 95 | this.members.singleWhere((member) => member.memberId == memberId); 96 | 97 | if(selectedMember.userId.isEmpty){ 98 | return ''; 99 | } 100 | 101 | return selectedMember.userId; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/model/member.dart: -------------------------------------------------------------------------------- 1 | class Member { 2 | String memberId; 3 | String name; 4 | String userId; 5 | 6 | Member({this.memberId, this.name, this.userId}); 7 | 8 | Map toJson() { 9 | return { 10 | '_id': this.memberId, 11 | 'name': this.name, 12 | }; 13 | } 14 | 15 | Member.fromJson(Map json) { 16 | this.memberId = json['_id']; 17 | this.name = json['name']; 18 | this.userId = json['userId']; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/model/owner.dart: -------------------------------------------------------------------------------- 1 | class Owner { 2 | String ownerId; 3 | String name; 4 | 5 | Owner({this.ownerId, this.name}); 6 | 7 | Map toJson() { 8 | return { 9 | '_id': this.ownerId, 10 | 'name': this.name, 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/model/participant.dart: -------------------------------------------------------------------------------- 1 | class Participant { 2 | String participantId; 3 | String name; 4 | int money; 5 | 6 | Participant({this.participantId, this.name, this.money}); 7 | 8 | Participant copyWith( 9 | {String participantId, 10 | String name, 11 | String money}) { 12 | return Participant( 13 | participantId: participantId ?? this.participantId, 14 | name: name ?? this.name, 15 | money: money ?? this.money); 16 | } 17 | 18 | Map toJson() { 19 | return { 20 | '_id': this.participantId, 21 | 'money': this.money, 22 | }; 23 | } 24 | 25 | factory Participant.fromJson(Map json) { 26 | return Participant( 27 | participantId: json['props']['memberId']['props']['value'], 28 | money: json['props']['amount']['props']['value']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/model/recurring-bill.dart: -------------------------------------------------------------------------------- 1 | class RecurringBill { 2 | String id; 3 | String billId; 4 | String groupId; 5 | DateTime date; 6 | int period; 7 | DateTime nextCreationDate; 8 | 9 | RecurringBill( 10 | {this.id, 11 | this.billId, 12 | this.groupId, 13 | this.date, 14 | this.period, 15 | }); 16 | 17 | Map toJson() { 18 | 19 | return { 20 | '_id': this.id, 21 | 'billId': this.billId, 22 | 'groupId': this.groupId, 23 | 'date': this.date.toIso8601String(), 24 | 'period': this.period, 25 | }; 26 | } 27 | 28 | RecurringBill.fromJson(Map json) { 29 | this.id = json['_id']; 30 | this.billId = json['billId']; 31 | this.groupId = json['groupId']; 32 | this.nextCreationDate = DateTime.parse(json['nextCreationDate']); 33 | } 34 | } -------------------------------------------------------------------------------- /lib/model/transaction.dart: -------------------------------------------------------------------------------- 1 | import 'package:uuid/uuid.dart'; 2 | 3 | class Transaction { 4 | final String senderId; 5 | final String beneficiaryId; 6 | final int money; 7 | final String currencyCode; 8 | final String groupId; 9 | 10 | Transaction( 11 | {this.senderId, 12 | this.beneficiaryId, 13 | this.money, 14 | this.currencyCode, 15 | this.groupId}); 16 | 17 | Map toJson() { 18 | var uuid = new Uuid(); 19 | 20 | return { 21 | '_id': uuid.v4(), 22 | 'senderId': this.senderId, 23 | 'beneficiaryId': this.beneficiaryId, 24 | 'money': this.money, 25 | 'currencyCode': this.currencyCode, 26 | 'groupId': this.groupId, 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/model/user.dart: -------------------------------------------------------------------------------- 1 | class User { 2 | String userId; 3 | String name; 4 | String email; 5 | String paypal; 6 | 7 | User({this.userId, this.name, this.email, this.paypal}); 8 | 9 | User copyWith({ 10 | String id, 11 | String name, 12 | String email, 13 | String paypal, 14 | }) { 15 | return User( 16 | userId: id ?? this.userId, 17 | name: name ?? this.name, 18 | email: email ?? this.email, 19 | paypal: paypal ?? this.paypal, 20 | ); 21 | } 22 | 23 | Map toJson() { 24 | return { 25 | '_id': this.userId, 26 | 'name': this.name, 27 | 'email': this.email, 28 | }; 29 | } 30 | 31 | User.fromJson(Map json) { 32 | this.userId = json['_id']; 33 | this.name = json['name']; 34 | this.email = json['email']; 35 | this.paypal = json['paypal']; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/redux/actions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:daruma/model/bill.dart'; 4 | import 'package:daruma/model/group.dart'; 5 | import 'package:daruma/model/member.dart'; 6 | import 'package:daruma/model/participant.dart'; 7 | import 'package:daruma/model/user.dart'; 8 | 9 | class LoginWithGoogleAction { 10 | final Completer completer; 11 | 12 | LoginWithGoogleAction({Completer completer}) 13 | : this.completer = completer ?? Completer(); 14 | } 15 | 16 | class LoginWithFacebookAction { 17 | final Completer completer; 18 | 19 | LoginWithFacebookAction({Completer completer}) 20 | : this.completer = completer ?? Completer(); 21 | } 22 | 23 | class LogoutAction { 24 | LogoutAction(); 25 | } 26 | 27 | class UserLoadedAction { 28 | final User user; 29 | final String photoUrl; 30 | final String tokenUserId; 31 | 32 | UserLoadedAction(this.user, this.photoUrl, this.tokenUserId); 33 | } 34 | 35 | class UserUpdatedAction { 36 | final String name; 37 | final String paypal; 38 | 39 | UserUpdatedAction(this.name, this.paypal); 40 | } 41 | 42 | class GroupUpdatedAction { 43 | final String name; 44 | final String currencyCode; 45 | 46 | GroupUpdatedAction(this.name, this.currencyCode); 47 | } 48 | 49 | class AddMemberToGroupAction { 50 | final Member member; 51 | 52 | AddMemberToGroupAction(this.member); 53 | } 54 | 55 | class DeleteMemberToGroupAction { 56 | final Member member; 57 | 58 | DeleteMemberToGroupAction(this.member); 59 | } 60 | 61 | class LoadingMembersSuccessAction { 62 | final List members; 63 | 64 | LoadingMembersSuccessAction(this.members); 65 | } 66 | 67 | class UserIsNew { 68 | final bool userIsNew; 69 | 70 | UserIsNew(this.userIsNew); 71 | } 72 | 73 | class StartLoadingGroupAction { 74 | StartLoadingGroupAction(); 75 | } 76 | 77 | class LoadingGroupSuccessAction { 78 | final Group group; 79 | 80 | LoadingGroupSuccessAction(this.group); 81 | } 82 | 83 | class LoadingGroupFailedAction { 84 | LoadingGroupFailedAction(); 85 | } 86 | 87 | class StartCreatingBillAction { 88 | final Group group; 89 | final String creatorId; 90 | 91 | StartCreatingBillAction(this.group, this.creatorId); 92 | } 93 | 94 | class StartEditingBillAction { 95 | final Bill bill; 96 | final Group group; 97 | 98 | StartEditingBillAction(this.bill, this.group); 99 | } 100 | 101 | class BillNameChangedAction { 102 | final String newName; 103 | 104 | BillNameChangedAction(this.newName); 105 | } 106 | 107 | class BillDateChangedAction { 108 | final DateTime newDate; 109 | 110 | BillDateChangedAction(this.newDate); 111 | } 112 | 113 | class BillMoneyChangedAction { 114 | final int newMoney; 115 | 116 | BillMoneyChangedAction(this.newMoney); 117 | } 118 | 119 | class BillDebtorWasAddedAction { 120 | final int index; 121 | 122 | BillDebtorWasAddedAction(this.index); 123 | } 124 | 125 | class BillDebtorWasDeletedAction { 126 | final int index; 127 | 128 | BillDebtorWasDeletedAction(this.index); 129 | } 130 | 131 | class RemoveNegatedDebtorsAction { 132 | RemoveNegatedDebtorsAction(); 133 | } 134 | 135 | class BillPayersChangedAction { 136 | final List newPayers; 137 | 138 | BillPayersChangedAction(this.newPayers); 139 | } 140 | 141 | class BillDebtorsChangedAction { 142 | final List newDebtors; 143 | 144 | BillDebtorsChangedAction(this.newDebtors); 145 | } 146 | -------------------------------------------------------------------------------- /lib/redux/index.dart: -------------------------------------------------------------------------------- 1 | export 'actions.dart'; 2 | export 'middleware.dart'; 3 | export 'reducer.dart'; 4 | export 'state.dart'; 5 | -------------------------------------------------------------------------------- /lib/redux/state.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/bill.dart'; 2 | import 'package:daruma/model/group.dart'; 3 | import 'package:daruma/model/user.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class AppState { 7 | final UserState userState; 8 | final GroupState groupState; 9 | final BillState billState; 10 | final bool userIsNew; 11 | 12 | const AppState({ 13 | @required this.userState, 14 | @required this.groupState, 15 | @required this.billState, 16 | @required this.userIsNew, 17 | }); 18 | 19 | AppState copyWith( 20 | {UserState userState, GroupState groupState, BillState billState}) { 21 | return new AppState( 22 | userState: userState ?? this.userState, 23 | groupState: groupState ?? this.groupState, 24 | billState: billState ?? this.billState, 25 | userIsNew: userIsNew ?? this.userIsNew); 26 | } 27 | 28 | factory AppState.initial() { 29 | return AppState( 30 | userState: UserState.initial(), 31 | groupState: GroupState.initial(), 32 | billState: BillState.initial(), 33 | userIsNew: false); 34 | } 35 | } 36 | 37 | @immutable 38 | class UserState { 39 | final User user; 40 | final String photoUrl; 41 | final String tokenUserId; 42 | 43 | const UserState( 44 | {@required this.user, 45 | @required this.photoUrl, 46 | @required this.tokenUserId}); 47 | 48 | factory UserState.initial() { 49 | return new UserState(user: null, photoUrl: null, tokenUserId: null); 50 | } 51 | 52 | UserState copyWith({User user, String photoUrl, String tokenUserId}) { 53 | return new UserState( 54 | user: user ?? this.user, 55 | photoUrl: photoUrl ?? this.photoUrl, 56 | tokenUserId: tokenUserId ?? this.tokenUserId); 57 | } 58 | } 59 | 60 | @immutable 61 | class GroupState { 62 | final Group group; 63 | final bool isLoading; 64 | final bool loadingError; 65 | 66 | GroupState({ 67 | @required this.group, 68 | @required this.isLoading, 69 | @required this.loadingError, 70 | }); 71 | 72 | factory GroupState.initial() { 73 | return new GroupState(group: null, isLoading: false, loadingError: false); 74 | } 75 | 76 | GroupState copyWith({bool isLoading, bool loadingError, Group group}) { 77 | return new GroupState( 78 | group: group ?? this.group, 79 | isLoading: isLoading ?? this.isLoading, 80 | loadingError: loadingError ?? this.loadingError, 81 | ); 82 | } 83 | } 84 | 85 | @immutable 86 | class BillState { 87 | final Bill bill; 88 | 89 | BillState({ 90 | @required this.bill, 91 | }); 92 | 93 | factory BillState.initial() { 94 | return new BillState(bill: Bill.initial()); 95 | } 96 | 97 | BillState copyWith({Bill bill}) { 98 | return new BillState(bill: bill ?? this.bill); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/redux/store.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/redux/index.dart'; 2 | import 'package:daruma/redux/state.dart'; 3 | import 'package:redux/redux.dart'; 4 | import 'package:redux_thunk/redux_thunk.dart'; 5 | 6 | Store createStore() { 7 | return Store( 8 | mainReducer, 9 | middleware: createMiddleware(), 10 | initialState: AppState.initial(), 11 | ); 12 | } 13 | 14 | List> createMiddleware() => 15 | >[thunkMiddleware, middleware]; 16 | -------------------------------------------------------------------------------- /lib/services/balance-calculator/balance-calculator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'package:daruma/model/group.dart'; 3 | import 'package:daruma/model/transaction.dart'; 4 | 5 | class BalanceCalculator { 6 | final HashMap transactions; 7 | final Group group; 8 | List finalTransactions; 9 | 10 | BalanceCalculator({this.transactions, this.group}) { 11 | finalTransactions = []; 12 | _calculateBalance(transactions); 13 | } 14 | 15 | void _calculateBalance(HashMap transactions) { 16 | var maxValue = 17 | transactions.values.reduce((curr, next) => curr > next ? curr : next); 18 | var minValue = 19 | transactions.values.reduce((curr, next) => curr < next ? curr : next); 20 | 21 | if (maxValue != minValue) { 22 | String maxKey = _getKeyFromValue(maxValue); 23 | String minKey = _getKeyFromValue(minValue); 24 | int result = maxValue + minValue; 25 | if ((result >= 0)) { 26 | finalTransactions.add(new Transaction( 27 | senderId: minKey, 28 | beneficiaryId: maxKey, 29 | money: minValue.abs(), 30 | currencyCode: group.currencyCode, 31 | groupId: group.groupId)); 32 | transactions.remove(maxKey); 33 | transactions.remove(minKey); 34 | transactions[maxKey] = result; 35 | transactions[minKey] = 0; 36 | } else { 37 | finalTransactions.add(new Transaction( 38 | senderId: minKey, 39 | beneficiaryId: maxKey, 40 | money: maxValue.abs(), 41 | currencyCode: group.currencyCode, 42 | groupId: group.groupId)); 43 | 44 | transactions.remove(maxKey); 45 | transactions.remove(minKey); 46 | transactions[maxKey] = 0; 47 | transactions[minKey] = result; 48 | } 49 | 50 | _calculateBalance(transactions); 51 | } 52 | } 53 | 54 | String _getKeyFromValue(int value) { 55 | return transactions.keys 56 | .firstWhere((k) => transactions[k] == value, orElse: () => null); 57 | } 58 | 59 | List getTransactions() { 60 | return finalTransactions; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/services/bloc/balance.bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:collection'; 3 | import 'package:daruma/model/group.dart'; 4 | import 'package:daruma/model/transaction.dart'; 5 | import 'package:daruma/services/balance-calculator/balance-calculator.dart'; 6 | import 'package:daruma/services/networking/index.dart'; 7 | import 'package:daruma/services/repository/balance.repository.dart'; 8 | import 'package:rxdart/subjects.dart'; 9 | 10 | class BalanceBloc { 11 | BalanceRepository _balanceRepository; 12 | StreamController _balanceController; 13 | 14 | StreamSink>> get balanceSink => 15 | _balanceController.sink; 16 | 17 | Stream>> get balanceStream => 18 | _balanceController.stream; 19 | 20 | BalanceBloc() { 21 | _balanceController = BehaviorSubject>>(); 22 | _balanceRepository = BalanceRepository(); 23 | } 24 | 25 | getBalance(String tokenId, Group group) async { 26 | balanceSink.add(Response.loading('Getting Balance of Group.')); 27 | try { 28 | HashMap balance = 29 | await _balanceRepository.getBalance(tokenId, group.groupId); 30 | 31 | BalanceCalculator balanceCalculator = 32 | new BalanceCalculator(transactions: balance, group: group); 33 | 34 | balanceSink.add(Response.completed(balanceCalculator.getTransactions())); 35 | } catch (e) { 36 | balanceSink.add(Response.error(e.toString())); 37 | print(e); 38 | } 39 | } 40 | 41 | dispose() { 42 | _balanceController?.close(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/services/bloc/bill.bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:daruma/model/bill.dart'; 4 | import 'package:daruma/model/recurring-bill.dart'; 5 | import 'package:daruma/services/networking/index.dart'; 6 | import 'package:daruma/services/repository/bill.repository.dart'; 7 | import 'package:rxdart/rxdart.dart'; 8 | 9 | class BillBloc { 10 | BillRepository _billRepository; 11 | StreamController _billController; 12 | StreamController _billControllerGroups; 13 | StreamController _recurringBillController; 14 | 15 | StreamSink> get billSink => _billController.sink; 16 | Stream> get billStream => _billController.stream; 17 | 18 | StreamSink>> get billSinkBills => 19 | _billControllerGroups.sink; 20 | Stream>> get billStreamBills => 21 | _billControllerGroups.stream; 22 | 23 | StreamSink>> get billSinkRecurringBills => 24 | _recurringBillController.sink; 25 | Stream>> get billStreamRecurringBills => 26 | _recurringBillController.stream; 27 | 28 | BillBloc() { 29 | _billController = BehaviorSubject>(); 30 | _billRepository = BillRepository(); 31 | _billControllerGroups = BehaviorSubject>>(); 32 | _recurringBillController = BehaviorSubject>>(); 33 | } 34 | 35 | postBill(Bill bill, String tokenId) async { 36 | billSink.add(Response.loading('Post new bill.')); 37 | try { 38 | bool billResponse = await _billRepository.createBill(bill, tokenId); 39 | billSink.add(Response.completed(billResponse)); 40 | } catch (e) { 41 | billSink.add(Response.error(e.toString())); 42 | print(e); 43 | } 44 | } 45 | 46 | postRecurringBill(RecurringBill recurringBill, String tokenId) async { 47 | billSink.add(Response.loading('Post new recurring bill.')); 48 | try { 49 | bool billResponse = await _billRepository.createRecurringBill(recurringBill, tokenId); 50 | billSink.add(Response.completed(billResponse)); 51 | } catch (e) { 52 | billSink.add(Response.error(e.toString())); 53 | print(e); 54 | } 55 | } 56 | 57 | deleteBill(String billId, String tokenId) async { 58 | billSink.add(Response.loading('Delete bill.')); 59 | try { 60 | bool billResponse = await _billRepository.deleteBill(billId, tokenId); 61 | billSink.add(Response.completed(billResponse)); 62 | } catch (e) { 63 | billSink.add(Response.error(e.toString())); 64 | print(e); 65 | } 66 | } 67 | 68 | deleteRecurringBill(String billId, String tokenId) async { 69 | billSink.add(Response.loading('Delete recurring bill.')); 70 | try { 71 | bool billResponse = await _billRepository.deleteRecurringBill(billId, tokenId); 72 | billSink.add(Response.completed(billResponse)); 73 | } catch (e) { 74 | billSink.add(Response.error(e.toString())); 75 | print(e); 76 | } 77 | } 78 | 79 | updateBill(Bill bill, String tokenId) async { 80 | billSink.add(Response.loading('Update bill.')); 81 | try { 82 | bool billResponse = 83 | await _billRepository.updateBill(bill, tokenId); 84 | billSink.add(Response.completed(billResponse)); 85 | } catch (e) { 86 | billSink.add(Response.error(e.toString())); 87 | print(e); 88 | } 89 | } 90 | 91 | updateRecurringBill(String recurringBillId, int period, String tokenId) async { 92 | billSink.add(Response.loading('Update recurring bill.')); 93 | try { 94 | bool billResponse = 95 | await _billRepository.updateRecurringBill(recurringBillId, period, tokenId); 96 | billSink.add(Response.completed(billResponse)); 97 | } catch (e) { 98 | billSink.add(Response.error(e.toString())); 99 | print(e); 100 | } 101 | } 102 | 103 | getBills(String groupId, String tokenId) async { 104 | billSinkBills.add(Response.loading('Get bills.')); 105 | try { 106 | List billResponse = 107 | await _billRepository.getBills(groupId, tokenId); 108 | 109 | billSinkBills.add(Response.completed(billResponse)); 110 | } catch (e) { 111 | billSinkBills.add(Response.error(e.toString())); 112 | print(e); 113 | } 114 | } 115 | 116 | getRecurringBills(String groupId, String tokenId) async { 117 | billSinkRecurringBills.add(Response.loading('Get recurring bills.')); 118 | try { 119 | List billResponse = 120 | await _billRepository.getRecurringBills(groupId, tokenId); 121 | 122 | billSinkRecurringBills.add(Response.completed(billResponse)); 123 | } catch (e) { 124 | billSinkRecurringBills.add(Response.error(e.toString())); 125 | print(e); 126 | } 127 | } 128 | 129 | dispose() { 130 | _billController?.close(); 131 | _billControllerGroups?.close(); 132 | _recurringBillController?.close(); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /lib/services/bloc/currency-list.bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:daruma/model/currency-list.dart'; 3 | import 'package:daruma/services/networking/index.dart'; 4 | import 'package:daruma/services/repository/currency-list.repository.dart'; 5 | import 'package:rxdart/subjects.dart'; 6 | 7 | class CurrencyListBloc { 8 | CurrencyListRepository _currencyListRepository; 9 | StreamController _currencyListController; 10 | 11 | StreamSink> get currencyListSink => 12 | _currencyListController.sink; 13 | 14 | Stream> get currencyListStream => 15 | _currencyListController.stream; 16 | 17 | CurrencyListBloc() { 18 | _currencyListController = BehaviorSubject>(); 19 | _currencyListRepository = CurrencyListRepository(); 20 | } 21 | 22 | fetchCurrencyList() async { 23 | currencyListSink.add(Response.loading('Getting Currency List.')); 24 | try { 25 | CurrencyList currencyList = 26 | await _currencyListRepository.fetchCurrencyList(); 27 | 28 | currencyListSink.add(Response.completed(currencyList)); 29 | } catch (e) { 30 | currencyListSink.add(Response.error(e.toString())); 31 | print(e); 32 | } 33 | } 34 | 35 | dispose() { 36 | _currencyListController?.close(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/services/bloc/group.bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:daruma/model/group.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/services/repository/group.repository.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | 8 | class GroupBloc { 9 | GroupRepository _groupRepository; 10 | StreamController _groupController; 11 | StreamController _groupControllerGroups; 12 | 13 | StreamSink> get groupSink => _groupController.sink; 14 | Stream> get groupStream => _groupController.stream; 15 | 16 | StreamSink>> get groupSinkGroups => 17 | _groupControllerGroups.sink; 18 | Stream>> get groupStreamGroups => 19 | _groupControllerGroups.stream; 20 | 21 | GroupBloc() { 22 | _groupController = BehaviorSubject>(); 23 | _groupRepository = GroupRepository(); 24 | _groupControllerGroups = BehaviorSubject>>(); 25 | } 26 | 27 | postGroup(Group group, String tokenId) async { 28 | groupSink.add(Response.loading('Post new group.')); 29 | try { 30 | bool groupResponse = await _groupRepository.createGroup(group, tokenId); 31 | groupSink.add(Response.completed(groupResponse)); 32 | } catch (e) { 33 | groupSink.add(Response.error(e.toString())); 34 | print(e); 35 | } 36 | } 37 | 38 | deleteGroup(String groupId, String tokenId) async { 39 | groupSink.add(Response.loading('Delete group.')); 40 | try { 41 | bool groupResponse = await _groupRepository.deleteGroup(groupId, tokenId); 42 | groupSink.add(Response.completed(groupResponse)); 43 | } catch (e) { 44 | groupSink.add(Response.error(e.toString())); 45 | print(e); 46 | } 47 | } 48 | 49 | updateGroup(String groupId, String name, String currencyCode, String tokenId) async { 50 | groupSink.add(Response.loading('Update group.')); 51 | try { 52 | bool groupResponse = 53 | await _groupRepository.updateGroup(groupId, name, currencyCode, tokenId); 54 | groupSink.add(Response.completed(groupResponse)); 55 | } catch (e) { 56 | groupSink.add(Response.error(e.toString())); 57 | print(e); 58 | } 59 | } 60 | 61 | getGroups(String tokenId) async { 62 | groupSinkGroups.add(Response.loading('Get groups.')); 63 | try { 64 | List groupResponse = await _groupRepository.getGroups(tokenId); 65 | 66 | groupSinkGroups.add(Response.completed(groupResponse)); 67 | } catch (e) { 68 | groupSinkGroups.add(Response.error(e.toString())); 69 | print(e); 70 | } 71 | } 72 | 73 | dispose() { 74 | _groupController?.close(); 75 | _groupControllerGroups?.close(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/services/bloc/member.bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:daruma/model/member.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/services/repository/member.repository.dart'; 6 | import 'package:rxdart/rxdart.dart'; 7 | 8 | class MemberBloc { 9 | MemberRepository _memberRepository; 10 | StreamController _memberController; 11 | StreamController _memberControllerMembers; 12 | 13 | StreamSink> get memberSink => _memberController.sink; 14 | Stream> get memberStream => _memberController.stream; 15 | 16 | StreamSink>> get memberSinkMembers => 17 | _memberControllerMembers.sink; 18 | Stream>> get memberStreamMembers => 19 | _memberControllerMembers.stream; 20 | 21 | MemberBloc() { 22 | _memberController = BehaviorSubject>(); 23 | _memberRepository = MemberRepository(); 24 | _memberControllerMembers = BehaviorSubject>>(); 25 | } 26 | 27 | addMember(Member member, String groupId, String tokenId) async { 28 | memberSink.add(Response.loading('Add new member.')); 29 | try { 30 | bool memberResponse = 31 | await _memberRepository.addMember(member, groupId, tokenId); 32 | memberSink.add(Response.completed(memberResponse)); 33 | } catch (e) { 34 | memberSink.add(Response.error(e.toString())); 35 | print(e); 36 | } 37 | } 38 | 39 | deleteMember(String memberId, String tokenId) async { 40 | memberSink.add(Response.loading('Delete member.')); 41 | try { 42 | bool memberResponse = 43 | await _memberRepository.deleteMember(memberId, tokenId); 44 | memberSink.add(Response.completed(memberResponse)); 45 | } catch (e) { 46 | memberSink.add(Response.error(e.toString())); 47 | print(e); 48 | } 49 | } 50 | 51 | getMembers(String groupId, String tokenId) async { 52 | memberSinkMembers.add(Response.loading('Get members.')); 53 | try { 54 | List memberResponse = 55 | await _memberRepository.getMembers(groupId, tokenId); 56 | 57 | memberSinkMembers.add(Response.completed(memberResponse)); 58 | } catch (e) { 59 | memberSinkMembers.add(Response.error(e.toString())); 60 | print(e); 61 | } 62 | } 63 | 64 | setUserIdToMember(String memberId, String userId, String tokenId) async { 65 | memberSink.add(Response.loading('Set User Id to member.')); 66 | try { 67 | bool memberResponse = 68 | await _memberRepository.setUserIdToMember(memberId, userId, tokenId); 69 | memberSink.add(Response.completed(memberResponse)); 70 | } catch (e) { 71 | memberSink.add(Response.error(e.toString())); 72 | print(e); 73 | } 74 | } 75 | 76 | dispose() { 77 | _memberController?.close(); 78 | _memberControllerMembers?.close(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/services/bloc/transaction.bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:daruma/model/transaction.dart'; 3 | import 'package:daruma/services/networking/index.dart'; 4 | import 'package:daruma/services/repository/transaction.repository.dart'; 5 | import 'package:rxdart/subjects.dart'; 6 | 7 | class TransactionBloc { 8 | TransactionRepository _transactionRepository; 9 | StreamController _transactionController; 10 | 11 | StreamSink> get transactionSink => _transactionController.sink; 12 | 13 | Stream> get transactionStream => _transactionController.stream; 14 | 15 | TransactionBloc() { 16 | _transactionController = BehaviorSubject>(); 17 | _transactionRepository = TransactionRepository(); 18 | } 19 | 20 | postTransaction(Transaction transaction, String tokenId) async { 21 | transactionSink.add(Response.loading('Post new transaction.')); 22 | try { 23 | bool transactionResponse = 24 | await _transactionRepository.createTransfer(transaction, tokenId); 25 | transactionSink.add(Response.completed(transactionResponse)); 26 | } catch (e) { 27 | transactionSink.add(Response.error(e.toString())); 28 | print(e); 29 | } 30 | } 31 | 32 | dispose() { 33 | _transactionController?.close(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/services/bloc/user.bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:daruma/services/networking/index.dart'; 3 | import 'package:daruma/services/repository/user.repository.dart'; 4 | import 'package:rxdart/subjects.dart'; 5 | 6 | class UserBloc { 7 | UserRepository _userRepository; 8 | StreamController _userController; 9 | 10 | StreamSink> get userSink => _userController.sink; 11 | 12 | Stream> get userStream => _userController.stream; 13 | 14 | UserBloc() { 15 | _userController = BehaviorSubject>(); 16 | _userRepository = UserRepository(); 17 | } 18 | 19 | updateUser(String userId, String name, String paypal, String tokenId) async { 20 | userSink.add(Response.loading('Update user.')); 21 | try { 22 | bool userResponse = 23 | await _userRepository.updateUser(userId, name, paypal, tokenId); 24 | userSink.add(Response.completed(userResponse)); 25 | } catch (e) { 26 | userSink.add(Response.error(e.toString())); 27 | print(e); 28 | } 29 | } 30 | 31 | dispose() { 32 | _userController?.close(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/services/dynamic_link/dynamic_links.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/ui/widget/select-member-in-group-dialog.widget.dart'; 2 | import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AppDynamicLinks { 6 | static void initDynamicLinks(BuildContext context) async { 7 | final PendingDynamicLinkData data = 8 | await FirebaseDynamicLinks.instance.getInitialLink(); 9 | final Uri deepLink = data?.link; 10 | 11 | if (deepLink != null && deepLink.queryParameters != null) { 12 | final type = deepLink.queryParameters['groupid'] ?? ''; 13 | print("Created:" + type); 14 | } 15 | 16 | FirebaseDynamicLinks.instance.onLink( 17 | onSuccess: (dynamicLink) async { 18 | final deepLink = dynamicLink?.link; 19 | 20 | if (deepLink != null && deepLink.queryParameters != null) { 21 | final type = deepLink.queryParameters['groupid'] ?? ''; 22 | print("Opened:" + type); 23 | if (type != '') { 24 | showDialog( 25 | context: context, 26 | builder: (_) { 27 | return new SimpleDialog( 28 | title: new Text("¿Quién eres?"), 29 | children: [ 30 | SelectMemberInGroupDialog(groupId: type), 31 | ]); 32 | }); 33 | } 34 | } 35 | }, 36 | onError: (e) async {}); 37 | } 38 | 39 | Future createDynamicLink(String groupId) async { 40 | final DynamicLinkParameters parameters = DynamicLinkParameters( 41 | uriPrefix: 'https://daruma.page.link/', 42 | link: Uri.parse('https://daruma.page.link/?groupid=$groupId'), 43 | androidParameters: AndroidParameters( 44 | packageName: 'com.tfg.daruma', 45 | minimumVersion: 0, 46 | ), 47 | dynamicLinkParametersOptions: DynamicLinkParametersOptions( 48 | shortDynamicLinkPathLength: ShortDynamicLinkPathLength.short, 49 | ), 50 | iosParameters: IosParameters( 51 | bundleId: 'com.google.FirebaseCppDynamicLinksTestApp.dev', 52 | minimumVersion: '0', 53 | ), 54 | ); 55 | 56 | final ShortDynamicLink shortLink = await parameters.buildShortLink(); 57 | return shortLink.shortUrl.toString(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/services/networking/api-provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/services/networking/custom-exception.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'dart:io'; 4 | import 'dart:convert'; 5 | import 'dart:async'; 6 | 7 | class ApiProvider { 8 | Future get(String url, {String header}) async { 9 | var responseJson; 10 | 11 | try { 12 | final response = await http.get( 13 | url, 14 | headers: {HttpHeaders.authorizationHeader: 'Bearer $header'}, 15 | ); 16 | responseJson = _response(response); 17 | } on SocketException { 18 | throw FetchDataException('No Internet connection'); 19 | } 20 | return responseJson; 21 | } 22 | 23 | Future post(String url, String body, String header) async { 24 | var responseJson; 25 | 26 | try { 27 | final response = await http.post(url, 28 | headers: { 29 | HttpHeaders.contentTypeHeader: 'application/json', 30 | HttpHeaders.authorizationHeader: 'Bearer $header' 31 | }, 32 | body: body); 33 | 34 | responseJson = _response(response); 35 | } on SocketException { 36 | throw FetchDataException('No Internet connection'); 37 | } 38 | return responseJson; 39 | } 40 | 41 | Future put(String url, String body, String header) async { 42 | var responseJson; 43 | 44 | try { 45 | final response = await http.put(url, 46 | headers: { 47 | HttpHeaders.contentTypeHeader: 'application/json', 48 | HttpHeaders.authorizationHeader: 'Bearer $header' 49 | }, 50 | body: body); 51 | 52 | responseJson = _response(response); 53 | } on SocketException { 54 | throw FetchDataException('No Internet connection'); 55 | } 56 | return responseJson; 57 | } 58 | 59 | Future patch(String url, String body, String header) async { 60 | var responseJson; 61 | 62 | try { 63 | final response = await http.patch(url, 64 | headers: { 65 | HttpHeaders.contentTypeHeader: 'application/json', 66 | HttpHeaders.authorizationHeader: 'Bearer $header' 67 | }, 68 | body: body); 69 | 70 | responseJson = _response(response); 71 | } on SocketException { 72 | throw FetchDataException('No Internet connection'); 73 | } 74 | return responseJson; 75 | } 76 | 77 | Future delete(String url, String header) async { 78 | var responseJson; 79 | try { 80 | final response = await http.delete(url, headers: { 81 | HttpHeaders.contentTypeHeader: 'application/json', 82 | HttpHeaders.authorizationHeader: 'Bearer $header' 83 | }); 84 | 85 | responseJson = _response(response); 86 | } on SocketException { 87 | throw FetchDataException('No Internet connection'); 88 | } 89 | return responseJson; 90 | } 91 | 92 | dynamic _response(http.Response response) { 93 | switch (response.statusCode) { 94 | case 200: 95 | var responseJson = json.decode(response.body); 96 | 97 | return responseJson; 98 | 99 | case 204: 100 | return true; 101 | case 400: 102 | throw BadRequestException(response.body); 103 | case 401: 104 | 105 | case 403: 106 | throw UnauthorisedException(response.body); 107 | 108 | case 404: 109 | return null; 110 | 111 | case 406: 112 | throw BadRequestException(response.body); 113 | case 500: 114 | 115 | default: 116 | throw FetchDataException( 117 | 'Error occured while Communication with Server with StatusCode : ${response.statusCode}'); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/services/networking/custom-exception.dart: -------------------------------------------------------------------------------- 1 | class CustomException implements Exception { 2 | final _message; 3 | final _prefix; 4 | 5 | CustomException([this._message, this._prefix]); 6 | 7 | String toString() { 8 | return "$_prefix$_message"; 9 | } 10 | } 11 | 12 | class FetchDataException extends CustomException { 13 | FetchDataException([String message]) 14 | : super(message, "Error During Communication: "); 15 | } 16 | 17 | class BadRequestException extends CustomException { 18 | BadRequestException([message]) : super(message, "Invalid Request: "); 19 | } 20 | 21 | class UnauthorisedException extends CustomException { 22 | UnauthorisedException([message]) : super(message, "Invalid Request: "); 23 | } 24 | 25 | class InvalidInputException extends CustomException { 26 | InvalidInputException([String message]) : super(message, "Invalid Request: "); 27 | } 28 | -------------------------------------------------------------------------------- /lib/services/networking/index.dart: -------------------------------------------------------------------------------- 1 | export 'api-provider.dart'; 2 | export 'custom-exception.dart'; 3 | export 'response.dart'; 4 | -------------------------------------------------------------------------------- /lib/services/networking/response.dart: -------------------------------------------------------------------------------- 1 | class Response { 2 | Status status; 3 | T data; 4 | String message; 5 | 6 | Response.loading(this.message) : status = Status.LOADING; 7 | Response.completed(this.data) : status = Status.COMPLETED; 8 | Response.error(this.message) : status = Status.ERROR; 9 | 10 | @override 11 | String toString() { 12 | return "Status : $status \n Message : $message \n Data : $data"; 13 | } 14 | } 15 | 16 | enum Status { LOADING, COMPLETED, ERROR } 17 | -------------------------------------------------------------------------------- /lib/services/repository/balance.repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:daruma/model/balance.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/util/url.dart'; 6 | 7 | class BalanceRepository { 8 | ApiProvider _provider = ApiProvider(); 9 | 10 | Future> getBalance( 11 | String tokenId, String groupId) async { 12 | final response = await _provider.get(Url.apiBaseUrl + "/balance/" + groupId, 13 | header: tokenId); 14 | 15 | var list = response as List; 16 | list = response.map((json) => Balance.fromJson(json)).toList(); 17 | 18 | HashMap result = HashMap.fromIterable(list, 19 | key: (e) => e.memberId, value: (e) => e.money); 20 | 21 | return result; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/services/repository/bill.repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:daruma/model/bill.dart'; 4 | import 'package:daruma/model/recurring-bill.dart'; 5 | import 'package:daruma/services/networking/index.dart'; 6 | import 'package:daruma/util/url.dart'; 7 | 8 | class BillRepository { 9 | ApiProvider _provider = ApiProvider(); 10 | 11 | Future createBill(Bill bill, String tokenId) async { 12 | final String url = Url.apiBaseUrl + "/bills"; 13 | 14 | var requestBody = jsonEncode(bill); 15 | final response = await _provider.post(url, requestBody, tokenId); 16 | 17 | return response; 18 | } 19 | 20 | Future createRecurringBill(RecurringBill bill, String tokenId) async { 21 | final String url = Url.apiBaseUrl + "/recurringbills"; 22 | 23 | var requestBody = jsonEncode(bill); 24 | final response = await _provider.post(url, requestBody, tokenId); 25 | 26 | return response; 27 | } 28 | 29 | Future updateBill(Bill bill, String tokenId) async { 30 | final String url = Url.apiBaseUrl + "/bills/" + bill.billId; 31 | 32 | var requestBody = jsonEncode(bill); 33 | final response = await _provider.put(url, requestBody, tokenId); 34 | 35 | return response; 36 | } 37 | 38 | Future updateRecurringBill(String recurringBillId, int period, String tokenId) async { 39 | final String url = Url.apiBaseUrl + "/recurringbills/" + recurringBillId; 40 | 41 | final Map body = { 42 | 'period': period, 43 | }; 44 | 45 | var requestBody = jsonEncode(body); 46 | final response = await _provider.patch(url, requestBody, tokenId); 47 | 48 | return response; 49 | } 50 | 51 | Future> getBills(String groupId, String tokenId) async { 52 | final response = await _provider.get(Url.apiBaseUrl + "/bills/" + groupId, 53 | header: tokenId); 54 | 55 | var list = response as List; 56 | list = response.map((json) => Bill.fromJson(json)).toList(); 57 | 58 | return list; 59 | } 60 | 61 | Future> getRecurringBills(String groupId, String tokenId) async { 62 | final response = await _provider.get(Url.apiBaseUrl + "/recurringbills/" + groupId, 63 | header: tokenId); 64 | 65 | var list = response as List; 66 | list = response.map((json) => RecurringBill.fromJson(json)).toList(); 67 | 68 | return list; 69 | } 70 | 71 | Future deleteBill(String billId, String tokenId) async { 72 | final response = 73 | await _provider.delete(Url.apiBaseUrl + "/bills/" + billId, tokenId); 74 | 75 | return response; 76 | } 77 | 78 | Future deleteRecurringBill(String billId, String tokenId) async { 79 | final response = 80 | await _provider.delete(Url.apiBaseUrl + "/recurringbills/" + billId, tokenId); 81 | 82 | return response; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/services/repository/currency-list.repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:daruma/model/currency-list.dart'; 3 | import 'package:daruma/services/networking/index.dart'; 4 | import 'package:daruma/util/url.dart'; 5 | 6 | class CurrencyListRepository { 7 | ApiProvider _provider = ApiProvider(); 8 | 9 | Future fetchCurrencyList() async { 10 | final response = await _provider.get(Url.currencyListBaseUrl); 11 | 12 | return CurrencyList.fromJson(response); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/services/repository/group.repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:daruma/model/group.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/util/url.dart'; 6 | 7 | class GroupRepository { 8 | ApiProvider _provider = ApiProvider(); 9 | 10 | Future createGroup(Group group, String tokenId) async { 11 | final String url = Url.apiBaseUrl + "/groups"; 12 | 13 | var requestBody = jsonEncode(group); 14 | final response = await _provider.post(url, requestBody, tokenId); 15 | 16 | return response; 17 | } 18 | 19 | Future> getGroups(String tokenId) async { 20 | final response = 21 | await _provider.get(Url.apiBaseUrl + "/groups", header: tokenId); 22 | 23 | var list = response as List; 24 | list = response.map((json) => Group.fromJson(json)).toList(); 25 | 26 | return list; 27 | } 28 | 29 | Future getGroup(String groupId, String tokenId) async { 30 | final response = await _provider.get(Url.apiBaseUrl + "/groups/" + groupId, 31 | header: tokenId); 32 | var group = Group.fromJson(response); 33 | 34 | return group; 35 | } 36 | 37 | Future deleteGroup(String groupId, String tokenId) async { 38 | final response = 39 | await _provider.delete(Url.apiBaseUrl + "/groups/" + groupId, tokenId); 40 | 41 | return response; 42 | } 43 | 44 | Future updateGroup( 45 | String groupId, String name, String currencyCode, String tokenId) async { 46 | final String url = Url.apiBaseUrl + "/groups/" + groupId; 47 | 48 | final Map body = { 49 | 'name': name, 50 | 'currencyCode': currencyCode, 51 | }; 52 | 53 | var requestBody = jsonEncode(body); 54 | 55 | final response = await _provider.patch(url, requestBody, tokenId); 56 | 57 | return response; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/services/repository/member.repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:daruma/model/member.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/util/url.dart'; 6 | 7 | class MemberRepository { 8 | ApiProvider _provider = ApiProvider(); 9 | 10 | Future> getMembers(String groupId, String tokenId) async { 11 | final response = await _provider.get(Url.apiBaseUrl + "/members/" + groupId, 12 | header: tokenId); 13 | 14 | var list = response as List; 15 | list = response.map((json) => Member.fromJson(json)).toList(); 16 | 17 | return list; 18 | } 19 | 20 | Future addMember(Member member, String groupId, String tokenId) async { 21 | final String url = Url.apiBaseUrl + "/members"; 22 | 23 | final Map body = { 24 | '_id': member.memberId, 25 | 'groupId': groupId, 26 | 'name': member.name, 27 | }; 28 | 29 | var requestBody = jsonEncode(body); 30 | final response = await _provider.post(url, requestBody, tokenId); 31 | 32 | return response; 33 | } 34 | 35 | Future deleteMember(String memberId, String tokenId) async { 36 | final response = await _provider.delete( 37 | Url.apiBaseUrl + "/members/" + memberId, tokenId); 38 | 39 | return response; 40 | } 41 | 42 | Future setUserIdToMember( 43 | String memberId, String userId, String tokenId) async { 44 | final String url = Url.apiBaseUrl + "/members/" + memberId; 45 | 46 | final Map body = {'userId': userId}; 47 | 48 | var requestBody = jsonEncode(body); 49 | 50 | final response = await _provider.patch(url, requestBody, tokenId); 51 | 52 | return response; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/services/repository/transaction.repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:daruma/model/transaction.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/util/url.dart'; 6 | 7 | class TransactionRepository { 8 | ApiProvider _provider = ApiProvider(); 9 | 10 | Future createTransfer(Transaction transaction, String tokenId) async { 11 | final String url = Url.apiBaseUrl + "/transactions"; 12 | 13 | var requestBody = jsonEncode(transaction); 14 | 15 | final response = await _provider.post(url, requestBody, tokenId); 16 | 17 | return response; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/services/repository/user.repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:daruma/model/user.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/util/url.dart'; 6 | 7 | class UserRepository { 8 | ApiProvider _provider = ApiProvider(); 9 | 10 | Future createUser(User user, String tokenId) async { 11 | final String url = Url.apiBaseUrl + "/users"; 12 | 13 | var requestBody = jsonEncode(user); 14 | final response = await _provider.post(url, requestBody, tokenId); 15 | 16 | return response; 17 | } 18 | 19 | Future getUser(String userId, String tokenId) async { 20 | final response = await _provider.get(Url.apiBaseUrl + "/users/" + userId, 21 | header: tokenId); 22 | 23 | var user; 24 | 25 | if (response != null) { 26 | user = User.fromJson(response); 27 | } 28 | 29 | return user; 30 | } 31 | 32 | Future updateUser( 33 | String userId, String name, String paypal, String tokenId) async { 34 | final String url = Url.apiBaseUrl + "/users/" + userId; 35 | 36 | final Map body = { 37 | 'name': name, 38 | 'paypal': paypal, 39 | }; 40 | 41 | var requestBody = jsonEncode(body); 42 | 43 | final response = await _provider.put(url, requestBody, tokenId); 44 | 45 | return response; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/ui/pages/add-members.page.dart: -------------------------------------------------------------------------------- 1 | import 'package:contacts_service/contacts_service.dart'; 2 | import 'package:daruma/util/colors.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AddMembersPage extends StatefulWidget { 6 | AddMembersPage({Key key, this.title}) : super(key: key); 7 | 8 | final String title; 9 | final String reloadLabel = 'Finalizar'; 10 | final String fireLabel = 'Confirmar'; 11 | final Color floatingButtonColor = redPrimaryColor; 12 | final IconData reloadIcon = Icons.refresh; 13 | final IconData fireIcon = Icons.check; 14 | 15 | @override 16 | _AddMembersPageState createState() => new _AddMembersPageState( 17 | floatingButtonLabel: this.fireLabel, 18 | icon: this.fireIcon, 19 | floatingButtonColor: this.floatingButtonColor, 20 | ); 21 | } 22 | 23 | class _AddMembersPageState extends State { 24 | List _contacts = new List(); 25 | List _uiCustomContacts = List(); 26 | List _allContacts = List(); 27 | bool _isLoading = false; 28 | bool _isSelectedContactsView = false; 29 | String floatingButtonLabel; 30 | Color floatingButtonColor; 31 | IconData icon; 32 | 33 | _AddMembersPageState({ 34 | this.floatingButtonLabel, 35 | this.icon, 36 | this.floatingButtonColor, 37 | }); 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | refreshContacts(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return new Scaffold( 48 | appBar: new AppBar( 49 | title: new Text(widget.title), 50 | backgroundColor: redPrimaryColor, 51 | ), 52 | body: !_isLoading 53 | ? Container( 54 | child: ListView.builder( 55 | itemCount: _uiCustomContacts?.length, 56 | itemBuilder: (BuildContext context, int index) { 57 | CustomContact _contact = _uiCustomContacts[index]; 58 | var _phonesList = _contact.contact.phones.toList(); 59 | 60 | return _buildListTile(_contact, _phonesList); 61 | }, 62 | ), 63 | ) 64 | : Center( 65 | child: CircularProgressIndicator(), 66 | ), 67 | floatingActionButton: new FloatingActionButton.extended( 68 | backgroundColor: floatingButtonColor, 69 | onPressed: () { 70 | if (!_isSelectedContactsView) { 71 | setState(() { 72 | _uiCustomContacts = _allContacts 73 | .where((contact) => contact.isChecked == true) 74 | .toList(); 75 | _isSelectedContactsView = true; 76 | _restateFloatingButton( 77 | widget.reloadLabel, 78 | Icons.refresh, 79 | Colors.green, 80 | ); 81 | }); 82 | } else { 83 | List members = new List(); 84 | for (var i = 0; i < _uiCustomContacts.length; i++) { 85 | members.add(_uiCustomContacts[i].contact); 86 | } 87 | 88 | Navigator.pop(context, members); 89 | } 90 | }, 91 | icon: Icon(icon), 92 | label: Text(floatingButtonLabel), 93 | ), 94 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, 95 | ); 96 | } 97 | 98 | ListTile _buildListTile(CustomContact c, List list) { 99 | return ListTile( 100 | title: Text(c.contact.displayName ?? ""), 101 | subtitle: list.length >= 1 && list[0]?.value != null 102 | ? Text(list[0].value) 103 | : Text(''), 104 | trailing: Checkbox( 105 | activeColor: Colors.green, 106 | value: c.isChecked, 107 | onChanged: (bool value) { 108 | setState(() { 109 | c.isChecked = value; 110 | }); 111 | }), 112 | ); 113 | } 114 | 115 | void _restateFloatingButton(String label, IconData icon, Color color) { 116 | floatingButtonLabel = label; 117 | icon = icon; 118 | floatingButtonColor = color; 119 | } 120 | 121 | refreshContacts() async { 122 | setState(() { 123 | _isLoading = true; 124 | }); 125 | var contacts = await ContactsService.getContacts(); 126 | _populateContacts(contacts); 127 | } 128 | 129 | void _populateContacts(Iterable contacts) { 130 | _contacts = contacts.where((item) => item.displayName != null).toList(); 131 | _contacts.sort((a, b) => a.displayName.compareTo(b.displayName)); 132 | _allContacts = 133 | _contacts.map((contact) => CustomContact(contact: contact)).toList(); 134 | setState(() { 135 | _uiCustomContacts = _allContacts; 136 | _isLoading = false; 137 | }); 138 | } 139 | } 140 | 141 | class CustomContact { 142 | final Contact contact; 143 | bool isChecked; 144 | 145 | CustomContact({ 146 | this.contact, 147 | this.isChecked = false, 148 | }); 149 | } 150 | -------------------------------------------------------------------------------- /lib/ui/pages/bills-history.page.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/group.dart'; 2 | import 'package:daruma/redux/index.dart'; 3 | import 'package:daruma/ui/widget/bills-list.widget.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_redux/flutter_redux.dart'; 6 | 7 | class BillsHistory extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return new StoreConnector( 11 | converter: (store) => _ViewModel( 12 | group: store.state.groupState.group, 13 | tokenId: store.state.userState.tokenUserId), 14 | builder: (BuildContext context, _ViewModel vm) => 15 | _historyView(context, vm), 16 | ); 17 | } 18 | 19 | Widget _historyView(BuildContext context, _ViewModel vm) { 20 | return Scaffold( 21 | body: SingleChildScrollView( 22 | child: SafeArea( 23 | child: Padding( 24 | padding: const EdgeInsets.all(20.0), 25 | child: Column( 26 | children: [ 27 | Row( 28 | children: [ 29 | BillsList(tokenId: vm.tokenId, group: vm.group), 30 | ], 31 | ) 32 | ], 33 | ), 34 | ), 35 | ), 36 | )); 37 | } 38 | } 39 | 40 | class _ViewModel { 41 | final Group group; 42 | final String tokenId; 43 | 44 | _ViewModel({ 45 | @required this.group, 46 | @required this.tokenId, 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /lib/ui/pages/group-balance.page.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/group.dart'; 2 | import 'package:daruma/redux/index.dart'; 3 | import 'package:daruma/ui/widget/balance-list.widget.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_redux/flutter_redux.dart'; 6 | 7 | class GroupBalance extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return new StoreConnector( 11 | converter: (store) => _ViewModel( 12 | group: store.state.groupState.group, 13 | tokenId: store.state.userState.tokenUserId), 14 | builder: (BuildContext context, _ViewModel vm) => 15 | _balanceView(context, vm), 16 | ); 17 | } 18 | 19 | Widget _balanceView(BuildContext context, _ViewModel vm) { 20 | return Scaffold( 21 | body: SingleChildScrollView( 22 | child: SafeArea( 23 | child: Padding( 24 | padding: const EdgeInsets.all(20.0), 25 | child: Column( 26 | children: [ 27 | Row( 28 | children: [ 29 | BalanceList(tokenId: vm.tokenId, group: vm.group), 30 | ], 31 | ) 32 | ], 33 | ), 34 | ), 35 | ), 36 | )); 37 | } 38 | } 39 | 40 | class _ViewModel { 41 | final Group group; 42 | final String tokenId; 43 | 44 | _ViewModel({ 45 | @required this.group, 46 | @required this.tokenId, 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /lib/ui/pages/group-bills.page.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/ui/pages/bills-history.page.dart'; 2 | import 'package:daruma/ui/pages/recurring-bills.page.dart'; 3 | import 'package:daruma/util/colors.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class BillsTabs extends StatelessWidget { 7 | const BillsTabs({Key key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final _kTabPages = [ 12 | BillsHistory(), 13 | RecurringBills(), 14 | ]; 15 | final _kTabs = [ 16 | Tab(child: Text('Historial', style: TextStyle(color: redPrimaryColor))), 17 | Tab(child: Text('Gastos recurrentes', style: TextStyle(color: redPrimaryColor))), 18 | ]; 19 | return DefaultTabController( 20 | length: _kTabs.length, 21 | child: Scaffold( 22 | appBar: PreferredSize( 23 | preferredSize: Size(double.infinity, 60), 24 | child: TabBar( 25 | indicatorColor: redPrimaryColor, 26 | tabs: _kTabs, 27 | ), 28 | ), 29 | body: TabBarView( 30 | children: _kTabPages, 31 | ), 32 | ), 33 | ); 34 | } 35 | } -------------------------------------------------------------------------------- /lib/ui/pages/group.page.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/ui/widget/bottom-navigation-bar.widget.dart'; 2 | import 'package:daruma/util/colors.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | 6 | class GroupPage extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return MaterialApp( 10 | title: 'Group', 11 | theme: ThemeData(primaryColor: redPrimaryColor, fontFamily: 'roboto'), 12 | home: ChangeNotifierProvider( 13 | child: BottomNavigationBarWidget(), 14 | create: (BuildContext context) => BottomNavigationBarProvider(), 15 | ), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/ui/pages/login.page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:daruma/model/user.dart'; 4 | import 'package:daruma/redux/index.dart'; 5 | import 'package:daruma/ui/widget/index.dart'; 6 | import 'package:daruma/util/colors.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:daruma/ui/pages/welcome.page.dart'; 9 | import 'package:flutter_redux/flutter_redux.dart'; 10 | 11 | class LoginPage extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return new StoreConnector(converter: (store) { 15 | return new _ViewModel( 16 | user: store.state.userState.user, 17 | loginGoogle: () { 18 | final result = LoginWithGoogleAction(); 19 | 20 | store.dispatch(result); 21 | 22 | Future.wait([result.completer.future]).then((user) => { 23 | Navigator.of(context).push( 24 | MaterialPageRoute( 25 | builder: (context) { 26 | return WelcomeScreen(); 27 | }, 28 | ), 29 | ) 30 | }); 31 | }, 32 | loginFacebook: () { 33 | final result = LoginWithFacebookAction(); 34 | 35 | store.dispatch(result); 36 | 37 | Future.wait([result.completer.future]).then((user) => { 38 | Navigator.of(context).push( 39 | MaterialPageRoute( 40 | builder: (context) { 41 | return WelcomeScreen(); 42 | }, 43 | ), 44 | ) 45 | }); 46 | }, 47 | ); 48 | }, builder: (BuildContext context, _ViewModel vm) { 49 | return _loginView(context, vm); 50 | }); 51 | } 52 | 53 | Widget _loginView(BuildContext context, _ViewModel vm) { 54 | return Scaffold( 55 | body: Container( 56 | color: redPrimaryColor, 57 | 58 | child: Center( 59 | child: Column( 60 | mainAxisSize: MainAxisSize.max, 61 | mainAxisAlignment: MainAxisAlignment.center, 62 | children: [ 63 | Image( 64 | image: AssetImage("assets/daruma-logo.png"), 65 | height: 200.0, 66 | ), 67 | SizedBox(height: 50), 68 | OAuthLoginButton( 69 | onPressed: vm.loginGoogle, 70 | text: "Inicia Sesión con Google", 71 | assetName: "assets/google_logo.png", 72 | backgroundColor: white, 73 | ), 74 | SizedBox(height: 20), 75 | OAuthLoginButton( 76 | onPressed: vm.loginFacebook, 77 | text: "Inicia Sesión con Facebook", 78 | assetName: "assets/facebook_logo.png", 79 | backgroundColor: white, 80 | ), 81 | ], 82 | ), 83 | ), 84 | ), 85 | ); 86 | } 87 | } 88 | 89 | class _ViewModel { 90 | final User user; 91 | final Function() loginGoogle; 92 | final Function() loginFacebook; 93 | 94 | _ViewModel({ 95 | @required this.user, 96 | @required this.loginGoogle, 97 | @required this.loginFacebook, 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /lib/ui/pages/recurring-bills.page.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/bill.dart'; 2 | import 'package:daruma/model/group.dart'; 3 | import 'package:daruma/redux/index.dart'; 4 | import 'package:daruma/services/bloc/bill.bloc.dart'; 5 | import 'package:daruma/services/networking/index.dart'; 6 | import 'package:daruma/ui/widget/recurring-bills-list.widget.dart'; 7 | import 'package:daruma/util/colors.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_redux/flutter_redux.dart'; 10 | import 'package:google_fonts/google_fonts.dart'; 11 | 12 | class RecurringBills extends StatelessWidget { 13 | @override 14 | Widget build(BuildContext context) { 15 | return new StoreConnector( 16 | converter: (store) => _ViewModel( 17 | group: store.state.groupState.group, 18 | tokenId: store.state.userState.tokenUserId), 19 | builder: (BuildContext context, _ViewModel vm) => 20 | _recurrenctBillsView(context, vm), 21 | ); 22 | } 23 | 24 | 25 | Widget _recurrenctBillsView(BuildContext context, _ViewModel vm) { 26 | 27 | final BillBloc _bloc = BillBloc(); 28 | _bloc.getBills(vm.group.groupId, vm.tokenId); 29 | 30 | return StreamBuilder>>( 31 | stream: _bloc.billStreamBills, 32 | builder: (context, snapshot) { 33 | if (snapshot.hasData) { 34 | switch (snapshot.data.status) { 35 | case Status.LOADING: 36 | return Center(child: CircularProgressIndicator()); 37 | break; 38 | 39 | case Status.COMPLETED: 40 | return Scaffold( 41 | body: SingleChildScrollView( 42 | child: SafeArea( 43 | child: Padding( 44 | padding: const EdgeInsets.all(20.0), 45 | child: Column( 46 | children: [ 47 | Row( 48 | children: [ 49 | RecurringBillsList(bills: snapshot.data.data), 50 | ], 51 | ) 52 | ], 53 | ), 54 | ), 55 | ), 56 | )); 57 | break; 58 | case Status.ERROR: 59 | final halfMediaWidth = MediaQuery.of(context).size.width / 1.2; 60 | return Padding( 61 | padding: const EdgeInsets.only(top: 18.0), 62 | child: Container( 63 | alignment: Alignment.topCenter, 64 | width: halfMediaWidth, 65 | child: Card( 66 | color: redPrimaryColor, 67 | child: Padding( 68 | padding: const EdgeInsets.all(8.0), 69 | child: Text( 70 | "Error de conexión recibiendo gastos", 71 | style: GoogleFonts.roboto( 72 | fontSize: 18, textStyle: TextStyle(color: white)), 73 | ), 74 | ), 75 | ), 76 | ), 77 | ); 78 | break; 79 | } 80 | } 81 | return Container(); 82 | }); 83 | } 84 | } 85 | 86 | class _ViewModel { 87 | final Group group; 88 | final String tokenId; 89 | 90 | _ViewModel({ 91 | @required this.group, 92 | @required this.tokenId, 93 | }); 94 | } -------------------------------------------------------------------------------- /lib/ui/widget/add-member-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/member.dart'; 2 | import 'package:daruma/redux/index.dart'; 3 | import 'package:daruma/redux/state.dart'; 4 | import 'package:daruma/services/bloc/member.bloc.dart'; 5 | import 'package:daruma/services/networking/index.dart'; 6 | import 'package:daruma/util/colors.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_redux/flutter_redux.dart'; 9 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 10 | 11 | class AddMemberDialog extends StatelessWidget { 12 | final Member member; 13 | final String groupId; 14 | 15 | AddMemberDialog({this.member, this.groupId}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return new StoreConnector(converter: (store) { 20 | return new _ViewModel( 21 | tokenId: store.state.userState.tokenUserId, 22 | addMember: (Member newMember) { 23 | store.dispatch(AddMemberToGroupAction(newMember)); 24 | }); 25 | }, builder: (BuildContext context, _ViewModel vm) { 26 | return _addMemberDialogView(context, vm); 27 | }); 28 | } 29 | 30 | Widget _addMemberDialogView(BuildContext context, _ViewModel vm) { 31 | final MemberBloc _bloc = MemberBloc(); 32 | 33 | _bloc.addMember(this.member, this.groupId, vm.tokenId); 34 | 35 | return StreamBuilder>( 36 | stream: _bloc.memberStream, 37 | builder: (context, snapshot) { 38 | if (snapshot.hasData) { 39 | switch (snapshot.data.status) { 40 | case Status.LOADING: 41 | return RichAlertDialog( 42 | alertTitle: richTitle("Cargando"), 43 | alertSubtitle: richSubtitle("Añadiendo miembro..."), 44 | alertType: RichAlertType.CUSTOM, 45 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 46 | ); 47 | break; 48 | 49 | case Status.COMPLETED: 50 | return RichAlertDialog( 51 | alertTitle: richTitle("¡Completado!"), 52 | alertSubtitle: richSubtitle("Miembro añadido correctamente"), 53 | alertType: RichAlertType.SUCCESS, 54 | actions: [ 55 | FlatButton( 56 | child: Text("OK"), 57 | onPressed: () { 58 | vm.addMember(this.member); 59 | Navigator.of(context).pop(); 60 | }, 61 | ) 62 | ], 63 | ); 64 | 65 | break; 66 | case Status.ERROR: 67 | 68 | var errorMessage = snapshot.data.message; 69 | var codeStatus = int.parse(errorMessage.substring(errorMessage.length-3)); 70 | 71 | var errorSubtitle = "Se ha producido un error"; 72 | 73 | if(codeStatus == 404){ 74 | errorSubtitle = "No se puede añadir un miembro en un grupo desconocido"; 75 | } else if(codeStatus == 409){ 76 | errorSubtitle = "Existe un miembro con el mismo nombre"; 77 | } 78 | 79 | return RichAlertDialog( 80 | alertTitle: richTitle("Error"), 81 | alertSubtitle: richSubtitle(errorSubtitle), 82 | alertType: RichAlertType.ERROR, 83 | actions: [ 84 | FlatButton( 85 | child: Text("OK"), 86 | onPressed: () { 87 | Navigator.pop(context, true); 88 | }, 89 | ) 90 | ], 91 | ); 92 | 93 | break; 94 | } 95 | } 96 | return Container(); 97 | }); 98 | } 99 | } 100 | 101 | class _ViewModel { 102 | final String tokenId; 103 | final Function(Member) addMember; 104 | 105 | _ViewModel({ 106 | @required this.tokenId, 107 | @required this.addMember, 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /lib/ui/widget/balance-list.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/group.dart'; 2 | import 'package:daruma/model/transaction.dart'; 3 | import 'package:daruma/services/bloc/balance.bloc.dart'; 4 | import 'package:daruma/services/networking/response.dart'; 5 | import 'package:daruma/ui/pages/transfer-money.page.dart'; 6 | import 'package:daruma/util/colors.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:google_fonts/google_fonts.dart'; 9 | 10 | class BalanceList extends StatelessWidget { 11 | final String tokenId; 12 | final Group group; 13 | 14 | BalanceList({this.tokenId, this.group}); 15 | 16 | Widget build(BuildContext context) { 17 | final BalanceBloc _bloc = new BalanceBloc(); 18 | _bloc.getBalance(tokenId, this.group); 19 | 20 | return StreamBuilder>>( 21 | stream: _bloc.balanceStream, 22 | builder: (context, snapshot) { 23 | if (snapshot.hasData) { 24 | switch (snapshot.data.status) { 25 | case Status.LOADING: 26 | return Center(child: CircularProgressIndicator()); 27 | break; 28 | 29 | case Status.COMPLETED: 30 | return Expanded( 31 | child: ListView.builder( 32 | shrinkWrap: true, 33 | physics: const NeverScrollableScrollPhysics(), 34 | itemCount: snapshot.data.data.length, 35 | itemBuilder: (context, index) { 36 | return _buildListTile( 37 | snapshot.data.data[index], group, context); 38 | }), 39 | ); 40 | break; 41 | case Status.ERROR: 42 | return Card( 43 | color: redPrimaryColor, 44 | child: Padding( 45 | padding: const EdgeInsets.all(16.0), 46 | child: Text( 47 | "Error recibiendo balances", 48 | style: GoogleFonts.roboto( 49 | fontSize: 22, textStyle: TextStyle(color: white)), 50 | ), 51 | ), 52 | ); 53 | break; 54 | } 55 | } 56 | return Container(); 57 | }); 58 | } 59 | 60 | Card _buildListTile( 61 | Transaction transaction, Group group, BuildContext context) { 62 | 63 | return Card( 64 | child: Padding( 65 | padding: const EdgeInsets.all(10.0), 66 | child: ListTile( 67 | title: Column( 68 | children: [ 69 | Row( 70 | children: [ 71 | Text( 72 | group.getMemberNameById(transaction.senderId) + 73 | " debe a " + 74 | group.getMemberNameById(transaction.beneficiaryId), 75 | style: GoogleFonts.roboto( 76 | textStyle: TextStyle(fontSize: 14, color: black)), 77 | ), 78 | ], 79 | ), 80 | Row( 81 | children: [ 82 | FlatButton( 83 | color: Colors.green, 84 | child: Text("Liquidar", 85 | style: GoogleFonts.roboto( 86 | fontSize: 15, textStyle: TextStyle(color: white))), 87 | onPressed: () { 88 | Navigator.of(context).push( 89 | MaterialPageRoute( 90 | builder: (context) { 91 | return TranferMoneyPage( 92 | transaction: transaction, group: group); 93 | }, 94 | ), 95 | ); 96 | }, 97 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(3.0))) 98 | ], 99 | ) 100 | ], 101 | ), 102 | trailing: Text( 103 | (transaction.money / 100).toString() + " " + group.currencyCode, style: GoogleFonts.roboto( 104 | textStyle: TextStyle(fontSize: 14, color: black))), 105 | ), 106 | ), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/ui/widget/bills-list.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/bill.dart'; 2 | import 'package:daruma/model/group.dart'; 3 | import 'package:daruma/services/bloc/bill.bloc.dart'; 4 | import 'package:daruma/services/networking/response.dart'; 5 | import 'package:daruma/ui/pages/detail-bill.page.dart'; 6 | import 'package:daruma/util/colors.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:google_fonts/google_fonts.dart'; 9 | 10 | class BillsList extends StatelessWidget { 11 | final String tokenId; 12 | final Group group; 13 | 14 | BillsList({this.tokenId, this.group}); 15 | 16 | Widget build(BuildContext context) { 17 | final BillBloc _bloc = BillBloc(); 18 | _bloc.getBills(this.group.groupId, tokenId); 19 | 20 | return StreamBuilder>>( 21 | stream: _bloc.billStreamBills, 22 | builder: (context, snapshot) { 23 | if (snapshot.hasData) { 24 | switch (snapshot.data.status) { 25 | case Status.LOADING: 26 | return Center(child: CircularProgressIndicator()); 27 | break; 28 | 29 | case Status.COMPLETED: 30 | return Expanded( 31 | child: ListView.builder( 32 | shrinkWrap: true, 33 | physics: const NeverScrollableScrollPhysics(), 34 | itemCount: snapshot.data.data.length, 35 | itemBuilder: (context, index) { 36 | return _buildListTile( 37 | snapshot.data.data[index], this.group, context); 38 | })); 39 | break; 40 | case Status.ERROR: 41 | final halfMediaWidth = MediaQuery.of(context).size.width / 1.2; 42 | return Container( 43 | alignment: Alignment.topCenter, 44 | width: halfMediaWidth, 45 | child: Card( 46 | color: redPrimaryColor, 47 | child: Padding( 48 | padding: const EdgeInsets.all(8.0), 49 | child: Text( 50 | "Error de conexión recibiendo gastos", 51 | style: GoogleFonts.roboto( 52 | fontSize: 18, textStyle: TextStyle(color: white)), 53 | ), 54 | ), 55 | ), 56 | ); 57 | break; 58 | } 59 | } 60 | return Container(); 61 | }); 62 | } 63 | 64 | Card _buildListTile(Bill bill, Group group, BuildContext context) { 65 | List payers = bill.payers 66 | .map((payer) => group.getMemberNameById(payer.participantId)) 67 | .toList(); 68 | 69 | return Card( 70 | child: Padding( 71 | padding: const EdgeInsets.only(left:10.0, right: 10.0), 72 | child: ListTile( 73 | contentPadding: const EdgeInsets.only(top: 0.0), 74 | leading: Padding( 75 | padding: const EdgeInsets.all(8.0), 76 | child: Icon(Icons.attach_money, size: 35.0), 77 | ), 78 | title: Text(bill.name), 79 | subtitle: Text("Pagado por " + 80 | payers.toString().substring(1, payers.toString().length - 1)), 81 | trailing: Text((bill.money / 100).toString() + " " + bill.currencyCode), 82 | onTap: () { 83 | Navigator.of(context).pop(); 84 | Navigator.of(context).push( 85 | MaterialPageRoute( 86 | builder: (context) { 87 | return DetailBillPage( 88 | bill: bill, group: group); 89 | }, 90 | ), 91 | ); 92 | }, 93 | ), 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/ui/widget/create-bill-floating-button.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/ui/pages/create-bill.page.dart'; 2 | import 'package:daruma/util/colors.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class NewBillFloatingButton extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return new FloatingActionButton( 9 | child: Icon(Icons.add), 10 | heroTag: null, 11 | backgroundColor: redPrimaryColor, 12 | onPressed: () { 13 | Navigator.pop(context, true); 14 | Navigator.of(context).push( 15 | MaterialPageRoute( 16 | builder: (context) { 17 | return CreateBillPage(); 18 | }, 19 | ), 20 | ); 21 | }, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/ui/widget/currency-button.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/ui/widget/currency-list.widget.dart'; 2 | import 'package:daruma/util/colors.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | 6 | class CurrencyButton extends StatefulWidget { 7 | final String currentCurrencyCode; 8 | final ValueChanged selectedCode; 9 | 10 | CurrencyButton({this.currentCurrencyCode, this.selectedCode}); 11 | 12 | @override 13 | _CurrencyButtonState createState() => _CurrencyButtonState(); 14 | } 15 | 16 | class _CurrencyButtonState extends State { 17 | String _currentCode; 18 | 19 | @override 20 | void initState() { 21 | _currentCode = widget.currentCurrencyCode; 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return FlatButton( 28 | color: redPrimaryColor, 29 | child: Padding( 30 | padding: const EdgeInsets.all(10.0), 31 | child: Row( 32 | mainAxisSize: MainAxisSize.min, 33 | children: [ 34 | Text( 35 | _currentCode, 36 | style: GoogleFonts.roboto( 37 | textStyle: TextStyle(fontSize: 17, color: Colors.white)), 38 | ), 39 | ], 40 | ), 41 | ), 42 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)), 43 | textColor: white, 44 | onPressed: () { 45 | showDialog( 46 | context: context, 47 | child: new SimpleDialog( 48 | title: new Text("Selecciona una divisa"), 49 | children: [ 50 | CurrenciesList( 51 | currentCurrencyCode: _currentCode, 52 | selectedCurrency: (currencyCode) { 53 | setState(() { 54 | _currentCode = currencyCode; 55 | }); 56 | widget.selectedCode(_currentCode); 57 | }), 58 | ])); 59 | }, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/ui/widget/currency-list.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/currency-list.dart'; 2 | import 'package:daruma/services/bloc/currency-list.bloc.dart'; 3 | import 'package:daruma/services/networking/response.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class CurrenciesList extends StatefulWidget { 7 | final String currentCurrencyCode; 8 | final ValueChanged selectedCurrency; 9 | 10 | CurrenciesList({Key key, this.currentCurrencyCode, this.selectedCurrency}) 11 | : super(key: key); 12 | 13 | @override 14 | _CurrenciesListState createState() => _CurrenciesListState(); 15 | } 16 | 17 | class _CurrenciesListState extends State { 18 | CurrencyListBloc _bloc; 19 | String _selectedCurrency; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | _selectedCurrency = widget.currentCurrencyCode; 25 | _bloc = CurrencyListBloc(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | _bloc.fetchCurrencyList(); 31 | 32 | return StreamBuilder>( 33 | stream: _bloc.currencyListStream, 34 | builder: (context, snapshot) { 35 | if (snapshot.hasData) { 36 | switch (snapshot.data.status) { 37 | case Status.LOADING: 38 | return Center(child: CircularProgressIndicator()); 39 | break; 40 | 41 | case Status.COMPLETED: 42 | return Container( 43 | height: 300.0, // Change as per your requirement 44 | width: 300.0, 45 | child: ListView.builder( 46 | itemCount: snapshot.data.data.rates.length, 47 | itemBuilder: (context, index) { 48 | return ListTile( 49 | title: Text(snapshot.data.data.rates[index].name), 50 | subtitle: 51 | Text(snapshot.data.data.rates[index].code), 52 | onTap: () { 53 | setState(() { 54 | _selectedCurrency = 55 | snapshot.data.data.rates[index].code; 56 | }); 57 | 58 | widget.selectedCurrency(_selectedCurrency); 59 | Navigator.pop(context, true); 60 | }); 61 | }), 62 | ); 63 | break; 64 | case Status.ERROR: 65 | return Center(child: CircularProgressIndicator()); 66 | break; 67 | } 68 | } 69 | return Container(); 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/ui/widget/delete-bill-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/redux/state.dart'; 2 | import 'package:daruma/services/bloc/bill.bloc.dart'; 3 | import 'package:daruma/services/networking/index.dart'; 4 | import 'package:daruma/ui/pages/group.page.dart'; 5 | import 'package:daruma/util/colors.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_redux/flutter_redux.dart'; 8 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 9 | 10 | class DeleteBillDialog extends StatelessWidget { 11 | final String billId; 12 | 13 | DeleteBillDialog({this.billId}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return new StoreConnector(converter: (store) { 18 | return new _ViewModel( 19 | tokenId: store.state.userState.tokenUserId, 20 | ); 21 | }, builder: (BuildContext context, _ViewModel vm) { 22 | return _deleteBillDialogView(context, vm); 23 | }); 24 | } 25 | 26 | Widget _deleteBillDialogView(BuildContext context, _ViewModel vm) { 27 | final BillBloc _bloc = BillBloc(); 28 | 29 | _bloc.deleteBill(this.billId, vm.tokenId); 30 | 31 | return StreamBuilder>( 32 | stream: _bloc.billStream, 33 | builder: (context, snapshot) { 34 | if (snapshot.hasData) { 35 | switch (snapshot.data.status) { 36 | case Status.LOADING: 37 | return RichAlertDialog( 38 | alertTitle: richTitle("Cargando"), 39 | alertSubtitle: richSubtitle("Se esta eliminando el gasto..."), 40 | alertType: RichAlertType.CUSTOM, 41 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 42 | ); 43 | break; 44 | 45 | case Status.COMPLETED: 46 | return RichAlertDialog( 47 | alertTitle: richTitle("¡Completado!"), 48 | alertSubtitle: richSubtitle("Gasto eliminado correctamente"), 49 | alertType: RichAlertType.SUCCESS, 50 | actions: [ 51 | FlatButton( 52 | child: Text("OK"), 53 | onPressed: () { 54 | Navigator.of(context).pop(); 55 | Navigator.of(context).push( 56 | MaterialPageRoute( 57 | builder: (context) { 58 | return GroupPage(); 59 | }, 60 | ), 61 | ); 62 | }, 63 | ) 64 | ], 65 | ); 66 | break; 67 | case Status.ERROR: 68 | var errorMessage = snapshot.data.message; 69 | var codeStatus = int.parse(errorMessage.substring(errorMessage.length-3)); 70 | 71 | var errorSubtitle = "Se ha producido un error"; 72 | 73 | if(codeStatus == 404){ 74 | errorSubtitle = "Gasto no encontrado"; 75 | } 76 | 77 | return RichAlertDialog( 78 | alertTitle: richTitle("Error"), 79 | alertSubtitle: richSubtitle(errorSubtitle), 80 | alertType: RichAlertType.ERROR, 81 | actions: [ 82 | FlatButton( 83 | child: Text("OK"), 84 | onPressed: () { 85 | Navigator.pop(context, true); 86 | }, 87 | ) 88 | ], 89 | ); 90 | break; 91 | } 92 | } 93 | return Container(); 94 | }); 95 | } 96 | } 97 | 98 | class _ViewModel { 99 | final String tokenId; 100 | 101 | _ViewModel({ 102 | @required this.tokenId, 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /lib/ui/widget/delete-group-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/redux/state.dart'; 2 | import 'package:daruma/services/bloc/group.bloc.dart'; 3 | import 'package:daruma/services/networking/index.dart'; 4 | import 'package:daruma/ui/pages/welcome.page.dart'; 5 | import 'package:daruma/util/colors.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_redux/flutter_redux.dart'; 8 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 9 | 10 | class DeleteDialog extends StatelessWidget { 11 | final String groupId; 12 | 13 | DeleteDialog({this.groupId}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return new StoreConnector(converter: (store) { 18 | return new _ViewModel( 19 | tokenId: store.state.userState.tokenUserId, 20 | ); 21 | }, builder: (BuildContext context, _ViewModel vm) { 22 | return _deleteDialogView(context, vm); 23 | }); 24 | } 25 | 26 | Widget _deleteDialogView(BuildContext context, _ViewModel vm) { 27 | final GroupBloc _bloc = GroupBloc(); 28 | 29 | _bloc.deleteGroup(this.groupId, vm.tokenId); 30 | 31 | return StreamBuilder>( 32 | stream: _bloc.groupStream, 33 | builder: (context, snapshot) { 34 | if (snapshot.hasData) { 35 | switch (snapshot.data.status) { 36 | case Status.LOADING: 37 | return RichAlertDialog( 38 | alertTitle: richTitle("Cargando"), 39 | alertSubtitle: richSubtitle("Se esta eliminando el grupo..."), 40 | alertType: RichAlertType.CUSTOM, 41 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 42 | ); 43 | break; 44 | 45 | case Status.COMPLETED: 46 | return RichAlertDialog( 47 | alertTitle: richTitle("¡Completado!"), 48 | alertSubtitle: richSubtitle("Grupo eliminado correctamente"), 49 | alertType: RichAlertType.SUCCESS, 50 | actions: [ 51 | FlatButton( 52 | child: Text("OK"), 53 | onPressed: () { 54 | Navigator.pop(context, true); 55 | Navigator.of(context).push( 56 | MaterialPageRoute( 57 | builder: (context) { 58 | return WelcomeScreen(); 59 | }, 60 | ), 61 | ); 62 | }, 63 | ) 64 | ], 65 | ); 66 | break; 67 | case Status.ERROR: 68 | var errorSubtitle = "Se ha producido un error"; 69 | 70 | return RichAlertDialog( 71 | alertTitle: richTitle("Error"), 72 | alertSubtitle: richSubtitle(errorSubtitle), 73 | alertType: RichAlertType.ERROR, 74 | actions: [ 75 | FlatButton( 76 | child: Text("OK"), 77 | onPressed: () { 78 | Navigator.pop(context, true); 79 | }, 80 | ) 81 | ], 82 | ); 83 | break; 84 | } 85 | } 86 | return Container(); 87 | }); 88 | } 89 | } 90 | 91 | class _ViewModel { 92 | final String tokenId; 93 | 94 | _ViewModel({ 95 | @required this.tokenId, 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /lib/ui/widget/delete-member-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/member.dart'; 2 | import 'package:daruma/redux/state.dart'; 3 | import 'package:daruma/redux/index.dart'; 4 | import 'package:daruma/services/bloc/member.bloc.dart'; 5 | import 'package:daruma/services/networking/index.dart'; 6 | import 'package:daruma/util/colors.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_redux/flutter_redux.dart'; 9 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 10 | 11 | class DeleteMemberDialog extends StatelessWidget { 12 | final Member member; 13 | 14 | DeleteMemberDialog({this.member}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return new StoreConnector(converter: (store) { 19 | return new _ViewModel( 20 | tokenId: store.state.userState.tokenUserId, 21 | deleteMember: (Member deletedMember) { 22 | store.dispatch(DeleteMemberToGroupAction(deletedMember)); 23 | }); 24 | }, builder: (BuildContext context, _ViewModel vm) { 25 | return _deleteMemberDialogView(context, vm); 26 | }); 27 | } 28 | 29 | Widget _deleteMemberDialogView(BuildContext context, _ViewModel vm) { 30 | final MemberBloc _bloc = MemberBloc(); 31 | 32 | _bloc.deleteMember(this.member.memberId, vm.tokenId); 33 | 34 | return StreamBuilder>( 35 | stream: _bloc.memberStream, 36 | builder: (context, snapshot) { 37 | if (snapshot.hasData) { 38 | switch (snapshot.data.status) { 39 | case Status.LOADING: 40 | return RichAlertDialog( 41 | alertTitle: richTitle("Cargando"), 42 | alertSubtitle: richSubtitle("Se esta eliminado el miembro..."), 43 | alertType: RichAlertType.CUSTOM, 44 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 45 | ); 46 | break; 47 | 48 | case Status.COMPLETED: 49 | return RichAlertDialog( 50 | alertTitle: richTitle("¡Completado!"), 51 | alertSubtitle: richSubtitle("Miembro eliminado correctamente"), 52 | alertType: RichAlertType.SUCCESS, 53 | actions: [ 54 | FlatButton( 55 | child: Text("OK"), 56 | onPressed: () { 57 | vm.deleteMember(this.member); 58 | Navigator.of(context).pop(); 59 | }, 60 | ) 61 | ], 62 | ); 63 | break; 64 | case Status.ERROR: 65 | var errorMessage = snapshot.data.message; 66 | var codeStatus = int.parse(errorMessage.substring(31, 34)); 67 | 68 | var errorSubtitle = "Se ha producido un error"; 69 | 70 | if(codeStatus == 403){ 71 | errorSubtitle = "No te puedes eliminar a ti mismo"; 72 | } else if(codeStatus == 404){ 73 | errorSubtitle = "No se puede eliminar "; 74 | } else if(codeStatus == 400){ 75 | errorSubtitle = "El miembro está involucrado en un gasto"; 76 | } 77 | 78 | return RichAlertDialog( 79 | alertTitle: richTitle("Error"), 80 | alertSubtitle: richSubtitle(errorSubtitle), 81 | alertType: RichAlertType.ERROR, 82 | actions: [ 83 | FlatButton( 84 | child: Text("OK"), 85 | onPressed: () { 86 | Navigator.pop(context, true); 87 | }, 88 | ) 89 | ], 90 | ); 91 | break; 92 | } 93 | } 94 | return Container(); 95 | }); 96 | } 97 | } 98 | 99 | class _ViewModel { 100 | final String tokenId; 101 | final Function(Member) deleteMember; 102 | 103 | _ViewModel({ 104 | @required this.tokenId, 105 | @required this.deleteMember, 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /lib/ui/widget/delete-recurring-bill-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/redux/state.dart'; 2 | import 'package:daruma/services/bloc/bill.bloc.dart'; 3 | import 'package:daruma/services/networking/index.dart'; 4 | import 'package:daruma/ui/pages/group.page.dart'; 5 | import 'package:daruma/util/colors.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_redux/flutter_redux.dart'; 8 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 9 | 10 | class DeleteRecurringBillDialog extends StatelessWidget { 11 | final String recurringBillId; 12 | 13 | DeleteRecurringBillDialog({this.recurringBillId}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return new StoreConnector(converter: (store) { 18 | return new _ViewModel( 19 | tokenId: store.state.userState.tokenUserId, 20 | ); 21 | }, builder: (BuildContext context, _ViewModel vm) { 22 | return _deleteRecurringBillDialogView(context, vm); 23 | }); 24 | } 25 | 26 | Widget _deleteRecurringBillDialogView(BuildContext context, _ViewModel vm) { 27 | final BillBloc _bloc = BillBloc(); 28 | 29 | _bloc.deleteRecurringBill(this.recurringBillId, vm.tokenId); 30 | 31 | return StreamBuilder>( 32 | stream: _bloc.billStream, 33 | builder: (context, snapshot) { 34 | if (snapshot.hasData) { 35 | switch (snapshot.data.status) { 36 | case Status.LOADING: 37 | return RichAlertDialog( 38 | alertTitle: richTitle("Cargando"), 39 | alertSubtitle: richSubtitle("Se esta eliminando el gasto recurrente..."), 40 | alertType: RichAlertType.CUSTOM, 41 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 42 | ); 43 | break; 44 | 45 | case Status.COMPLETED: 46 | return RichAlertDialog( 47 | alertTitle: richTitle("¡Completado!"), 48 | alertSubtitle: richSubtitle("Gasto recurrente eliminado correctamente"), 49 | alertType: RichAlertType.SUCCESS, 50 | actions: [ 51 | FlatButton( 52 | child: Text("OK"), 53 | onPressed: () { 54 | Navigator.of(context).pop(); 55 | Navigator.of(context).push( 56 | MaterialPageRoute( 57 | builder: (context) { 58 | return GroupPage(); 59 | }, 60 | ), 61 | ); 62 | }, 63 | ) 64 | ], 65 | ); 66 | break; 67 | case Status.ERROR: 68 | var errorMessage = snapshot.data.message; 69 | var codeStatus = int.parse(errorMessage.substring(errorMessage.length-3)); 70 | 71 | var errorSubtitle = "Se ha producido un error"; 72 | 73 | if(codeStatus == 404){ 74 | errorSubtitle = "Gasto no encontrado"; 75 | } 76 | 77 | return RichAlertDialog( 78 | alertTitle: richTitle("Error"), 79 | alertSubtitle: richSubtitle(errorSubtitle), 80 | alertType: RichAlertType.ERROR, 81 | actions: [ 82 | FlatButton( 83 | child: Text("OK"), 84 | onPressed: () { 85 | Navigator.pop(context, true); 86 | }, 87 | ) 88 | ], 89 | ); 90 | break; 91 | } 92 | } 93 | return Container(); 94 | }); 95 | } 96 | } 97 | 98 | class _ViewModel { 99 | final String tokenId; 100 | 101 | _ViewModel({ 102 | @required this.tokenId, 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /lib/ui/widget/detail-bill-app-bar-title.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class BillAppBarTitle extends StatelessWidget { 5 | 6 | final double barHeight = 66.0; 7 | final String title; 8 | 9 | const BillAppBarTitle({this.title}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | child: Row( 15 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 16 | children: [ 17 | Container(child: Text( 18 | title, 19 | style: TextStyle( 20 | color: Colors.white, 21 | fontFamily: 'Poppins', 22 | fontSize: 20.0 23 | ), 24 | ),), 25 | ], 26 | ), 27 | ); 28 | } 29 | } -------------------------------------------------------------------------------- /lib/ui/widget/detail-bill-flexible-app-bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/util/colors.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | 5 | class BillFlexibleAppBar extends StatelessWidget { 6 | 7 | final String title; 8 | final String price; 9 | final String bottomLeftSubtitle; 10 | final String bottomRightSubtitle; 11 | 12 | final double appBarHeight = 66.0; 13 | 14 | const BillFlexibleAppBar({this.title, this.price, this.bottomLeftSubtitle,this.bottomRightSubtitle}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final double statusBarHeight = MediaQuery 19 | .of(context) 20 | .padding 21 | .top; 22 | 23 | return new Container( 24 | padding: new EdgeInsets.only(top: statusBarHeight), 25 | height: statusBarHeight + appBarHeight, 26 | child: new Center( 27 | child: Column( 28 | mainAxisAlignment: MainAxisAlignment.end, 29 | children: [ 30 | Container( 31 | child: Column( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | children: [ 34 | Container(child: new Text( 35 | this.title, 36 | style: const TextStyle( 37 | color: Colors.white, 38 | fontFamily: 'Poppins', 39 | fontSize: 28.0 40 | ) 41 | ),), 42 | Container(child: new Text( 43 | this.price, 44 | style: const TextStyle( 45 | color: Colors.white, 46 | fontFamily: 'Poppins', 47 | fontWeight: FontWeight.w800, 48 | fontSize: 36.0 49 | ) 50 | ),), 51 | ],),), 52 | 53 | 54 | Container( 55 | child: Row( 56 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 57 | children: [ 58 | Container(child: Padding( 59 | padding: const EdgeInsets.only(bottom: 8.0,left:8.0), 60 | child: new Text( 61 | this.bottomLeftSubtitle, 62 | style: const TextStyle( 63 | color: Colors.white70, 64 | fontFamily: 'Poppins', 65 | fontSize: 16.0 66 | ) 67 | ), 68 | ),), 69 | 70 | Container(child: Padding( 71 | padding: const EdgeInsets.only(bottom: 8.0,right:8.0), 72 | child: Container( 73 | child: Row(children: [ 74 | Container(child: Text( 75 | this.bottomRightSubtitle, style: const TextStyle( 76 | color: Colors.white70, 77 | fontFamily: 'Poppins', 78 | fontSize: 16.0 79 | ),),), 80 | ],) 81 | 82 | ), 83 | ),), 84 | 85 | 86 | ],), 87 | ), 88 | ],) 89 | ), 90 | decoration: new BoxDecoration( 91 | color: redPrimaryColor, 92 | ), 93 | ); 94 | } 95 | } -------------------------------------------------------------------------------- /lib/ui/widget/edit-bill-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/bill.dart'; 2 | import 'package:daruma/redux/state.dart'; 3 | import 'package:daruma/services/bloc/bill.bloc.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/ui/pages/group.page.dart'; 6 | import 'package:daruma/util/colors.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_redux/flutter_redux.dart'; 9 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 10 | 11 | class EditBillDialog extends StatelessWidget { 12 | final Bill bill; 13 | 14 | EditBillDialog({this.bill}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return new StoreConnector(converter: (store) { 19 | return new _ViewModel( 20 | tokenId: store.state.userState.tokenUserId, 21 | ); 22 | }, builder: (BuildContext context, _ViewModel vm) { 23 | return _editDialogView(context, vm); 24 | }); 25 | } 26 | 27 | Widget _editDialogView(BuildContext context, _ViewModel vm) { 28 | final BillBloc _bloc = BillBloc(); 29 | 30 | _bloc.updateBill(this.bill, vm.tokenId); 31 | 32 | return StreamBuilder>( 33 | stream: _bloc.billStream, 34 | builder: (context, snapshot) { 35 | if (snapshot.hasData) { 36 | switch (snapshot.data.status) { 37 | case Status.LOADING: 38 | return RichAlertDialog( 39 | alertTitle: richTitle("Cargando"), 40 | alertSubtitle: richSubtitle("Se esta actualizando el gasto..."), 41 | alertType: RichAlertType.CUSTOM, 42 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 43 | ); 44 | break; 45 | 46 | case Status.COMPLETED: 47 | return RichAlertDialog( 48 | alertTitle: richTitle("¡Completado!"), 49 | alertSubtitle: richSubtitle("Gasto actualizado correctamente"), 50 | alertType: RichAlertType.SUCCESS, 51 | actions: [ 52 | FlatButton( 53 | child: Text("OK"), 54 | onPressed: () { 55 | Navigator.of(context).pop(); 56 | Navigator.of(context).push( 57 | MaterialPageRoute( 58 | builder: (context) { 59 | return GroupPage(); 60 | }, 61 | ), 62 | ); 63 | }, 64 | ) 65 | ], 66 | ); 67 | break; 68 | case Status.ERROR: 69 | return RichAlertDialog( 70 | alertTitle: richTitle("Error"), 71 | alertSubtitle: richSubtitle("Error actualizando el gasto"), 72 | alertType: RichAlertType.ERROR, 73 | actions: [ 74 | FlatButton( 75 | child: Text("OK"), 76 | onPressed: () { 77 | Navigator.pop(context, true); 78 | }, 79 | ) 80 | ], 81 | ); 82 | break; 83 | } 84 | } 85 | return Container(); 86 | }); 87 | } 88 | } 89 | 90 | class _ViewModel { 91 | final String tokenId; 92 | 93 | _ViewModel({@required this.tokenId}); 94 | } 95 | -------------------------------------------------------------------------------- /lib/ui/widget/edit-group-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/group.dart'; 2 | import 'package:daruma/redux/actions.dart'; 3 | import 'package:daruma/redux/state.dart'; 4 | import 'package:daruma/services/bloc/group.bloc.dart'; 5 | import 'package:daruma/services/networking/index.dart'; 6 | import 'package:daruma/ui/pages/group.page.dart'; 7 | import 'package:daruma/util/colors.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_redux/flutter_redux.dart'; 10 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 11 | 12 | class EditGroupDialog extends StatelessWidget { 13 | final String name; 14 | final String currencyCode; 15 | 16 | EditGroupDialog({this.name, this.currencyCode}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return new StoreConnector(converter: (store) { 21 | return new _ViewModel( 22 | group: store.state.groupState.group, 23 | tokenId: store.state.userState.tokenUserId, 24 | updateGroup: () { 25 | store.dispatch(GroupUpdatedAction(name, currencyCode)); 26 | }); 27 | }, builder: (BuildContext context, _ViewModel vm) { 28 | return _editGroupNameDialogView(context, vm); 29 | }); 30 | } 31 | 32 | Widget _editGroupNameDialogView(BuildContext context, _ViewModel vm) { 33 | final GroupBloc _bloc = GroupBloc(); 34 | 35 | _bloc.updateGroup(vm.group.groupId, name, currencyCode, vm.tokenId); 36 | 37 | return StreamBuilder>( 38 | stream: _bloc.groupStream, 39 | builder: (context, snapshot) { 40 | if (snapshot.hasData) { 41 | switch (snapshot.data.status) { 42 | case Status.LOADING: 43 | return RichAlertDialog( 44 | alertTitle: richTitle("Cargando"), 45 | alertSubtitle: richSubtitle("Se esta editando el grupo..."), 46 | alertType: RichAlertType.CUSTOM, 47 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 48 | ); 49 | break; 50 | 51 | case Status.COMPLETED: 52 | return RichAlertDialog( 53 | alertTitle: richTitle("¡Completado!"), 54 | alertSubtitle: richSubtitle("Group editado correctamente"), 55 | alertType: RichAlertType.SUCCESS, 56 | actions: [ 57 | FlatButton( 58 | child: Text("OK"), 59 | onPressed: () { 60 | vm.updateGroup(); 61 | Navigator.pop(context, true); 62 | Navigator.pop(context, true); 63 | Navigator.of(context).push( 64 | MaterialPageRoute( 65 | builder: (context) { 66 | return GroupPage(); 67 | }, 68 | ), 69 | ); 70 | }, 71 | ) 72 | ], 73 | ); 74 | break; 75 | case Status.ERROR: 76 | var errorSubtitle = "Se ha producido un error"; 77 | 78 | return RichAlertDialog( 79 | alertTitle: richTitle("Error"), 80 | alertSubtitle: richSubtitle(errorSubtitle), 81 | alertType: RichAlertType.ERROR, 82 | actions: [ 83 | FlatButton( 84 | child: Text("OK"), 85 | onPressed: () { 86 | Navigator.pop(context, true); 87 | }, 88 | ) 89 | ], 90 | ); 91 | break; 92 | } 93 | } 94 | return Container(); 95 | }); 96 | } 97 | } 98 | 99 | class _ViewModel { 100 | final Group group; 101 | final String tokenId; 102 | final Function updateGroup; 103 | 104 | _ViewModel({ 105 | @required this.group, 106 | @required this.tokenId, 107 | @required this.updateGroup, 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /lib/ui/widget/edit-profile-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/user.dart'; 2 | import 'package:daruma/redux/actions.dart'; 3 | import 'package:daruma/redux/state.dart'; 4 | import 'package:daruma/services/bloc/user.bloc.dart'; 5 | import 'package:daruma/services/networking/index.dart'; 6 | import 'package:daruma/ui/pages/welcome.page.dart'; 7 | import 'package:daruma/util/colors.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_redux/flutter_redux.dart'; 10 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 11 | 12 | class EditProfileDialog extends StatelessWidget { 13 | final String name; 14 | final String paypal; 15 | 16 | EditProfileDialog({this.name, this.paypal}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return new StoreConnector(converter: (store) { 21 | return new _ViewModel( 22 | user: store.state.userState.user, 23 | tokenId: store.state.userState.tokenUserId, 24 | updateUser: () { 25 | store.dispatch(UserUpdatedAction(name, paypal)); 26 | }); 27 | }, builder: (BuildContext context, _ViewModel vm) { 28 | return _editProfileDialogView(context, vm); 29 | }); 30 | } 31 | 32 | Widget _editProfileDialogView(BuildContext context, _ViewModel vm) { 33 | final UserBloc _bloc = UserBloc(); 34 | 35 | _bloc.updateUser(vm.user.userId, name, paypal, vm.tokenId); 36 | 37 | return StreamBuilder>( 38 | stream: _bloc.userStream, 39 | builder: (context, snapshot) { 40 | if (snapshot.hasData) { 41 | switch (snapshot.data.status) { 42 | case Status.LOADING: 43 | return RichAlertDialog( 44 | alertTitle: richTitle("Cargando"), 45 | alertSubtitle: richSubtitle("Se esta editando el perfil..."), 46 | alertType: RichAlertType.CUSTOM, 47 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 48 | ); 49 | break; 50 | 51 | case Status.COMPLETED: 52 | return RichAlertDialog( 53 | alertTitle: richTitle("¡Completado!"), 54 | alertSubtitle: richSubtitle("Perfil editado correctamente"), 55 | alertType: RichAlertType.SUCCESS, 56 | actions: [ 57 | FlatButton( 58 | child: Text("OK"), 59 | onPressed: () { 60 | vm.updateUser(); 61 | Navigator.of(context).push( 62 | MaterialPageRoute( 63 | builder: (context) { 64 | return WelcomeScreen(); 65 | }, 66 | ), 67 | ); 68 | }, 69 | ) 70 | ], 71 | ); 72 | break; 73 | case Status.ERROR: 74 | 75 | var errorSubtitle = "No se ha encontrado al usuario"; 76 | 77 | return RichAlertDialog( 78 | alertTitle: richTitle("Error"), 79 | alertSubtitle: richSubtitle(errorSubtitle), 80 | alertType: RichAlertType.ERROR, 81 | actions: [ 82 | FlatButton( 83 | child: Text("OK"), 84 | onPressed: () { 85 | Navigator.pop(context, true); 86 | }, 87 | ) 88 | ], 89 | ); 90 | break; 91 | } 92 | } 93 | return Container(); 94 | }); 95 | } 96 | } 97 | 98 | class _ViewModel { 99 | final User user; 100 | final String tokenId; 101 | final Function updateUser; 102 | 103 | _ViewModel({ 104 | @required this.user, 105 | @required this.tokenId, 106 | @required this.updateUser, 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /lib/ui/widget/edit-recurring-bill-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/redux/state.dart'; 2 | import 'package:daruma/services/bloc/bill.bloc.dart'; 3 | import 'package:daruma/services/networking/index.dart'; 4 | import 'package:daruma/ui/pages/group.page.dart'; 5 | import 'package:daruma/util/colors.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_redux/flutter_redux.dart'; 8 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 9 | 10 | class EditRecurringBillDialog extends StatelessWidget { 11 | final String recurringBillId; 12 | final int period; 13 | 14 | EditRecurringBillDialog({this.recurringBillId, this.period}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return new StoreConnector(converter: (store) { 19 | return new _ViewModel( 20 | tokenId: store.state.userState.tokenUserId, 21 | ); 22 | }, builder: (BuildContext context, _ViewModel vm) { 23 | return _editRecurringBillDialogView(context, vm); 24 | }); 25 | } 26 | 27 | Widget _editRecurringBillDialogView(BuildContext context, _ViewModel vm) { 28 | final BillBloc _bloc = BillBloc(); 29 | 30 | _bloc.updateRecurringBill(this.recurringBillId, this.period, vm.tokenId); 31 | 32 | return StreamBuilder>( 33 | stream: _bloc.billStream, 34 | builder: (context, snapshot) { 35 | if (snapshot.hasData) { 36 | switch (snapshot.data.status) { 37 | case Status.LOADING: 38 | return RichAlertDialog( 39 | alertTitle: richTitle("Cargando"), 40 | alertSubtitle: richSubtitle("Se esta actualizando el gasto..."), 41 | alertType: RichAlertType.CUSTOM, 42 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 43 | ); 44 | break; 45 | 46 | case Status.COMPLETED: 47 | return RichAlertDialog( 48 | alertTitle: richTitle("¡Completado!"), 49 | alertSubtitle: richSubtitle("Periodo actualizado correctamente"), 50 | alertType: RichAlertType.SUCCESS, 51 | actions: [ 52 | FlatButton( 53 | child: Text("OK"), 54 | onPressed: () { 55 | Navigator.of(context).pop(); 56 | Navigator.of(context).push( 57 | MaterialPageRoute( 58 | builder: (context) { 59 | return GroupPage(); 60 | }, 61 | ), 62 | ); 63 | }, 64 | ) 65 | ], 66 | ); 67 | break; 68 | case Status.ERROR: 69 | return RichAlertDialog( 70 | alertTitle: richTitle("Error"), 71 | alertSubtitle: richSubtitle("Error actualizando el periodo"), 72 | alertType: RichAlertType.ERROR, 73 | actions: [ 74 | FlatButton( 75 | child: Text("OK"), 76 | onPressed: () { 77 | Navigator.pop(context, true); 78 | }, 79 | ) 80 | ], 81 | ); 82 | break; 83 | } 84 | } 85 | return Container(); 86 | }); 87 | } 88 | } 89 | 90 | class _ViewModel { 91 | final String tokenId; 92 | 93 | _ViewModel({@required this.tokenId}); 94 | } -------------------------------------------------------------------------------- /lib/ui/widget/group-button.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/group.dart'; 2 | import 'package:daruma/redux/index.dart'; 3 | import 'package:daruma/services/dynamic_link/dynamic_links.dart'; 4 | import 'package:daruma/util/colors.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_redux/flutter_redux.dart'; 7 | import 'package:google_fonts/google_fonts.dart'; 8 | import 'package:redux/redux.dart'; 9 | 10 | class GroupButton extends StatelessWidget { 11 | final String groupId; 12 | final String name; 13 | 14 | const GroupButton({ 15 | this.groupId, 16 | this.name, 17 | Key key, 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return new StoreConnector( 23 | converter: (store) => _ViewModel.fromStore(store), 24 | builder: (BuildContext context, _ViewModel vm) { 25 | return _groupButtonView(context, vm); 26 | }); 27 | } 28 | 29 | Widget _groupButtonView(BuildContext context, _ViewModel vm) { 30 | return FlatButton( 31 | color: redPrimaryColor, 32 | onPressed: () async { 33 | vm.load(groupId, vm.tokenId); 34 | 35 | if (vm.loadingError) { 36 | showDialog( 37 | context: context, 38 | child: new SimpleDialog(children: [ 39 | Container( 40 | height: 300.0, // Change as per your requirement 41 | width: 300.0, 42 | child: Row( 43 | children: [ 44 | Text("Error loading groups"), 45 | FlatButton( 46 | onPressed: () { 47 | Navigator.pop(context, true); 48 | }, 49 | child: Text( 50 | "Exit", 51 | ), 52 | ) 53 | ], 54 | ), 55 | ) 56 | ])); 57 | } else if (vm.isLoading) { 58 | showDialog( 59 | context: context, 60 | child: new SimpleDialog(children: [ 61 | Center(child: CircularProgressIndicator()) 62 | ])); 63 | } 64 | }, 65 | child: Padding( 66 | padding: const EdgeInsets.all(16.0), 67 | child: Text( 68 | name, 69 | style: GoogleFonts.roboto( 70 | fontSize: 22, textStyle: TextStyle(color: white)), 71 | ), 72 | ), 73 | ); 74 | } 75 | 76 | Future getDynamicLinkForGroup(String groupId) async { 77 | final AppDynamicLinks _appDynamicLinks = AppDynamicLinks(); 78 | return _appDynamicLinks.createDynamicLink(groupId); 79 | } 80 | } 81 | 82 | class _ViewModel { 83 | final String tokenId; 84 | final bool isLoading; 85 | final bool loadingError; 86 | final Group group; 87 | final Function(String, String) load; 88 | 89 | _ViewModel({ 90 | this.tokenId, 91 | this.isLoading, 92 | this.loadingError, 93 | this.group, 94 | this.load, 95 | }); 96 | 97 | static _ViewModel fromStore(Store store) { 98 | return _ViewModel( 99 | tokenId: store.state.userState.tokenUserId, 100 | isLoading: store.state.groupState.isLoading, 101 | loadingError: store.state.groupState.loadingError, 102 | group: store.state.groupState.group, 103 | load: (String groupId, String tokenId) { 104 | store.dispatch(loadGroup(groupId, tokenId)); 105 | }, 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/ui/widget/groups-list.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/group.dart'; 2 | import 'package:daruma/services/bloc/group.bloc.dart'; 3 | import 'package:daruma/services/networking/response.dart'; 4 | import 'package:daruma/ui/widget/group-button.widget.dart'; 5 | import 'package:daruma/util/colors.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:google_fonts/google_fonts.dart'; 8 | 9 | class GroupsList extends StatelessWidget { 10 | final String tokenId; 11 | 12 | GroupsList({this.tokenId}); 13 | 14 | Widget build(BuildContext context) { 15 | final GroupBloc _bloc = GroupBloc(); 16 | _bloc.getGroups(tokenId); 17 | 18 | return StreamBuilder>>( 19 | stream: _bloc.groupStreamGroups, 20 | builder: (context, snapshot) { 21 | if (snapshot.hasData) { 22 | switch (snapshot.data.status) { 23 | case Status.LOADING: 24 | return Center(child: CircularProgressIndicator()); 25 | break; 26 | 27 | case Status.COMPLETED: 28 | return Expanded( 29 | child: ListView.builder( 30 | shrinkWrap: true, 31 | physics: const NeverScrollableScrollPhysics(), 32 | itemCount: snapshot.data.data.length, 33 | itemBuilder: (context, index) { 34 | return Padding( 35 | padding: const EdgeInsets.only( 36 | left: 8.0, 37 | right: 25.0, 38 | top: 8.0, 39 | bottom: 8.0), 40 | child: GroupButton( 41 | groupId: snapshot.data.data[index].groupId, 42 | name: snapshot.data.data[index].name), 43 | ); 44 | }), 45 | ); 46 | break; 47 | case Status.ERROR: 48 | return Card( 49 | color: redPrimaryColor, 50 | child: Padding( 51 | padding: const EdgeInsets.all(16.0), 52 | child: Text( 53 | "Error recibiendo grupos", 54 | style: GoogleFonts.roboto( 55 | fontSize: 22, textStyle: TextStyle(color: white)), 56 | ), 57 | ), 58 | ); 59 | break; 60 | } 61 | } 62 | return Container(); 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/ui/widget/index.dart: -------------------------------------------------------------------------------- 1 | export 'balance-list.widget.dart'; 2 | export 'bills-list.widget.dart'; 3 | export 'bottom-navigation-bar.widget.dart'; 4 | export 'create-bill-floating-button.widget.dart'; 5 | export 'currency-button.widget.dart'; 6 | export 'currency-list.widget.dart'; 7 | export 'edit-profile-dialog.widget.dart'; 8 | export 'group-button.widget.dart'; 9 | export 'groups-list.widget.dart'; 10 | export 'members-button.widget.dart'; 11 | export 'number-form-field.widget.dart'; 12 | export 'oauth-login-button.widget.dart'; 13 | export 'post-bill-dialog.widget.dart'; 14 | export 'post-group-dialog.widget.dart'; 15 | export 'post-transfer-dialog.widget.dart'; 16 | export 'sign-out-button.widget.dart'; 17 | export 'text-form-field.widget.dart'; 18 | -------------------------------------------------------------------------------- /lib/ui/widget/members-button.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/member.dart'; 2 | import 'package:daruma/util/colors.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:grouped_buttons/grouped_buttons.dart'; 6 | 7 | class MembersButton extends StatefulWidget { 8 | final List members; 9 | final ValueChanged> selectedMembers; 10 | 11 | MembersButton({this.members, this.selectedMembers}); 12 | 13 | @override 14 | _MembersButtonState createState() => _MembersButtonState(); 15 | } 16 | 17 | class _MembersButtonState extends State { 18 | List membersNames = []; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return FlatButton( 28 | color: redPrimaryColor, 29 | child: Text( 30 | membersNames.isEmpty 31 | ? widget.members.first.name 32 | : (membersNames.length.toString() + "+ PERSONAS"), 33 | style: GoogleFonts.roboto( 34 | textStyle: TextStyle(fontSize: 15, color: Colors.white)), 35 | ), 36 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)), 37 | textColor: white, 38 | onPressed: () { 39 | showDialog( 40 | context: context, 41 | child: new SimpleDialog( 42 | title: new Text("Elige el pagador"), 43 | children: [ 44 | CheckboxGroup( 45 | labels: widget.members.map((member) => member.name).toList(), 46 | onSelected: (List checked) => membersNames = checked, 47 | activeColor: redPrimaryColor, 48 | ), 49 | FlatButton( 50 | onPressed: () { 51 | widget.selectedMembers(membersNames); 52 | Navigator.of(context, rootNavigator: true).pop(); 53 | }, 54 | child: Text("Guardar")) 55 | ], 56 | ), 57 | ); 58 | }, 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/ui/widget/members-list.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/group.dart'; 2 | import 'package:daruma/model/member.dart'; 3 | import 'package:daruma/redux/index.dart'; 4 | import 'package:daruma/ui/widget/delete-member-dialog.widget.dart'; 5 | import 'package:daruma/util/colors.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_redux/flutter_redux.dart'; 8 | 9 | class MembersList extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return new StoreConnector(converter: (store) { 13 | return new _ViewModel( 14 | tokenId: store.state.userState.tokenUserId, 15 | group: store.state.groupState.group, 16 | ); 17 | }, builder: (BuildContext context, _ViewModel vm) { 18 | return _membersListView(context, vm); 19 | }); 20 | } 21 | 22 | Widget _membersListView(BuildContext context, _ViewModel vm) { 23 | return Expanded( 24 | child: ListView.builder( 25 | shrinkWrap: true, 26 | physics: const NeverScrollableScrollPhysics(), 27 | itemCount: vm.group.members.length, 28 | itemBuilder: (context, index) { 29 | return _buildListTile(vm.group.members[index], context); 30 | })); 31 | } 32 | 33 | ListTile _buildListTile(Member member, BuildContext context) { 34 | return ListTile( 35 | contentPadding: const EdgeInsets.all(1.0), 36 | title: Text(member.name), 37 | trailing: IconButton( 38 | icon: Icon( 39 | Icons.clear, 40 | color: black, 41 | ), 42 | onPressed: () { 43 | showDialog( 44 | context: context, 45 | builder: (__) { 46 | return DeleteMemberDialog(member: member); 47 | }); 48 | }, 49 | ), 50 | ); 51 | } 52 | } 53 | 54 | class _ViewModel { 55 | final String tokenId; 56 | final Group group; 57 | 58 | _ViewModel({ 59 | @required this.tokenId, 60 | @required this.group, 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/ui/widget/number-form-field.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_multi_formatter/formatters/money_input_formatter.dart'; 4 | 5 | class CustomNumberFormField extends StatelessWidget { 6 | final String hintText; 7 | final String initialValue; 8 | final FormFieldSetter validator; 9 | final FormFieldSetter onChanged; 10 | 11 | CustomNumberFormField({this.hintText, this.initialValue, this.validator, this.onChanged}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return TextFormField( 16 | decoration: InputDecoration( 17 | hintText: hintText, 18 | contentPadding: EdgeInsets.all(1.0), 19 | filled: true, 20 | fillColor: Colors.transparent, 21 | 22 | ), 23 | validator: validator, 24 | onChanged: onChanged, 25 | initialValue: initialValue, 26 | inputFormatters: [ 27 | MoneyInputFormatter(), 28 | BlacklistingTextInputFormatter(new RegExp('-')), 29 | ], 30 | keyboardType: TextInputType.number, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/ui/widget/oauth-login-button.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/util/colors.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class OAuthLoginButton extends StatelessWidget { 5 | final Function() onPressed; 6 | final String text; 7 | final String assetName; 8 | final Color backgroundColor; 9 | 10 | OAuthLoginButton( 11 | {this.onPressed, this.text, this.assetName, this.backgroundColor}); 12 | 13 | Widget build(BuildContext context) { 14 | return OutlineButton( 15 | splashColor: backgroundColor, 16 | onPressed: onPressed, 17 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)), 18 | highlightElevation: 0, 19 | borderSide: BorderSide(color: white), 20 | child: Padding( 21 | padding: const EdgeInsets.fromLTRB(0, 10, 0, 10), 22 | child: Row( 23 | mainAxisSize: MainAxisSize.min, 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | Image(image: AssetImage(assetName), height: 35.0), 27 | Padding( 28 | padding: const EdgeInsets.only(left: 10), 29 | child: Text( 30 | text, 31 | style: TextStyle( 32 | fontSize: 20, 33 | color: white, 34 | ), 35 | ), 36 | ) 37 | ], 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/ui/widget/post-bill-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/bill.dart'; 2 | import 'package:daruma/model/recurring-bill.dart'; 3 | import 'package:daruma/redux/state.dart'; 4 | import 'package:daruma/services/bloc/bill.bloc.dart'; 5 | import 'package:daruma/services/networking/index.dart'; 6 | import 'package:daruma/ui/pages/group.page.dart'; 7 | import 'package:daruma/ui/widget/post-recurring-bill-dialog.widget.dart'; 8 | import 'package:daruma/util/colors.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_redux/flutter_redux.dart'; 11 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 12 | import 'package:uuid/uuid.dart'; 13 | 14 | class PostBillDialog extends StatelessWidget { 15 | final Bill bill; 16 | final int period; 17 | 18 | PostBillDialog({this.bill, this.period}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return new StoreConnector(converter: (store) { 23 | return new _ViewModel( 24 | tokenId: store.state.userState.tokenUserId, 25 | ); 26 | }, builder: (BuildContext context, _ViewModel vm) { 27 | return _postDialogView(context, vm); 28 | }); 29 | } 30 | 31 | Widget _postDialogView(BuildContext context, _ViewModel vm) { 32 | final BillBloc _bloc = BillBloc(); 33 | 34 | _bloc.postBill(this.bill, vm.tokenId); 35 | 36 | return StreamBuilder>( 37 | stream: _bloc.billStream, 38 | builder: (context, snapshot) { 39 | if (snapshot.hasData) { 40 | switch (snapshot.data.status) { 41 | case Status.LOADING: 42 | return RichAlertDialog( 43 | alertTitle: richTitle("Cargando"), 44 | alertSubtitle: richSubtitle("Se esta creando el gasto..."), 45 | alertType: RichAlertType.CUSTOM, 46 | dialogIcon: Icon( 47 | Icons.access_time, 48 | color: redPrimaryColor, 49 | ), 50 | ); 51 | break; 52 | 53 | case Status.COMPLETED: 54 | if (this.period == 0) { 55 | return RichAlertDialog( 56 | alertTitle: richTitle("¡Completado!"), 57 | alertSubtitle: richSubtitle("Gasto creado correctamente"), 58 | alertType: RichAlertType.SUCCESS, 59 | actions: [ 60 | FlatButton( 61 | child: Text("OK"), 62 | onPressed: () { 63 | Navigator.of(context).pop(); 64 | Navigator.of(context).push( 65 | MaterialPageRoute( 66 | builder: (context) { 67 | return GroupPage(); 68 | }, 69 | ), 70 | ); 71 | }, 72 | ) 73 | ], 74 | ); 75 | } else { 76 | var uuid = new Uuid(); 77 | 78 | RecurringBill newRecurringBill = new RecurringBill( 79 | id: uuid.v4(), 80 | billId: this.bill.billId, 81 | groupId: this.bill.groupId, 82 | date: this.bill.date, 83 | period: this.period); 84 | 85 | return PostRecurringBillDialog( 86 | recurringBill: newRecurringBill); 87 | } 88 | 89 | break; 90 | case Status.ERROR: 91 | return RichAlertDialog( 92 | alertTitle: richTitle("Error"), 93 | alertSubtitle: richSubtitle("Error creando el gasto"), 94 | alertType: RichAlertType.ERROR, 95 | actions: [ 96 | FlatButton( 97 | child: Text("OK"), 98 | onPressed: () { 99 | Navigator.pop(context, true); 100 | }, 101 | ) 102 | ], 103 | ); 104 | break; 105 | } 106 | } 107 | return Container(); 108 | }); 109 | } 110 | } 111 | 112 | class _ViewModel { 113 | final String tokenId; 114 | 115 | _ViewModel({@required this.tokenId}); 116 | } 117 | -------------------------------------------------------------------------------- /lib/ui/widget/post-group-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/group.dart'; 2 | import 'package:daruma/model/owner.dart'; 3 | import 'package:daruma/model/user.dart'; 4 | import 'package:daruma/redux/state.dart'; 5 | import 'package:daruma/services/bloc/group.bloc.dart'; 6 | import 'package:daruma/services/networking/index.dart'; 7 | import 'package:daruma/ui/pages/welcome.page.dart'; 8 | import 'package:daruma/util/colors.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_redux/flutter_redux.dart'; 11 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 12 | 13 | class PostGroupDialog extends StatelessWidget { 14 | final Group group; 15 | 16 | PostGroupDialog({this.group}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return new StoreConnector(converter: (store) { 21 | return new _ViewModel( 22 | user: store.state.userState.user, 23 | tokenId: store.state.userState.tokenUserId, 24 | ); 25 | }, builder: (BuildContext context, _ViewModel vm) { 26 | return _postDialogView(context, vm); 27 | }); 28 | } 29 | 30 | Widget _postDialogView(BuildContext context, _ViewModel vm) { 31 | final GroupBloc _bloc = GroupBloc(); 32 | 33 | Owner owner = new Owner(); 34 | owner.ownerId = vm.user.userId; 35 | owner.name = vm.user.name; 36 | 37 | _bloc.postGroup( 38 | Group( 39 | groupId: group.groupId, 40 | name: group.name, 41 | currencyCode: group.currencyCode, 42 | owner: owner, 43 | members: group.members, 44 | ), 45 | vm.tokenId); 46 | 47 | return StreamBuilder>( 48 | stream: _bloc.groupStream, 49 | builder: (context, snapshot) { 50 | if (snapshot.hasData) { 51 | switch (snapshot.data.status) { 52 | case Status.LOADING: 53 | return RichAlertDialog( 54 | alertTitle: richTitle("Cargando"), 55 | alertSubtitle: richSubtitle("Se esta creando el grupo..."), 56 | alertType: RichAlertType.CUSTOM, 57 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 58 | ); 59 | break; 60 | 61 | case Status.COMPLETED: 62 | return RichAlertDialog( 63 | alertTitle: richTitle("¡Completado!"), 64 | alertSubtitle: richSubtitle("Grupo creado correctamente"), 65 | alertType: RichAlertType.SUCCESS, 66 | actions: [ 67 | FlatButton( 68 | child: Text("OK"), 69 | onPressed: () { 70 | Navigator.pop(context, true); 71 | Navigator.of(context).push( 72 | MaterialPageRoute( 73 | builder: (context) { 74 | return WelcomeScreen(); 75 | }, 76 | ), 77 | ); 78 | }, 79 | ) 80 | ], 81 | ); 82 | break; 83 | case Status.ERROR: 84 | var errorMessage = snapshot.data.message; 85 | print("ERROR:" + errorMessage); 86 | var codeStatus; 87 | 88 | if(errorMessage == "Error During Communication: No Internet connection"){ 89 | codeStatus = 404; 90 | } 91 | else{ 92 | codeStatus = int.parse(errorMessage.substring(errorMessage.length-3)); 93 | } 94 | 95 | var errorSubtitle = "Se ha producido un error"; 96 | 97 | if(codeStatus == 409){ 98 | errorSubtitle = "Existe un grupo con el mismo nombre"; 99 | } 100 | else if(codeStatus == 404){ 101 | errorSubtitle = "Error de conexión"; 102 | } 103 | 104 | return RichAlertDialog( 105 | alertTitle: richTitle("Error"), 106 | alertSubtitle: richSubtitle(errorSubtitle), 107 | alertType: RichAlertType.ERROR, 108 | actions: [ 109 | FlatButton( 110 | child: Text("OK"), 111 | onPressed: () { 112 | Navigator.pop(context, true); 113 | }, 114 | ) 115 | ], 116 | ); 117 | break; 118 | } 119 | } 120 | return Container(); 121 | }); 122 | } 123 | } 124 | 125 | class _ViewModel { 126 | final User user; 127 | final String tokenId; 128 | 129 | _ViewModel({ 130 | @required this.user, 131 | @required this.tokenId, 132 | }); 133 | } 134 | -------------------------------------------------------------------------------- /lib/ui/widget/post-recurring-bill-dialog.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/recurring-bill.dart'; 2 | import 'package:daruma/redux/state.dart'; 3 | import 'package:daruma/services/bloc/bill.bloc.dart'; 4 | import 'package:daruma/services/networking/index.dart'; 5 | import 'package:daruma/ui/pages/group.page.dart'; 6 | import 'package:daruma/util/colors.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_redux/flutter_redux.dart'; 9 | import 'package:sweet_alert_dialogs/sweet_alert_dialogs.dart'; 10 | 11 | class PostRecurringBillDialog extends StatelessWidget { 12 | final RecurringBill recurringBill; 13 | 14 | PostRecurringBillDialog({this.recurringBill}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return new StoreConnector(converter: (store) { 19 | return new _ViewModel( 20 | tokenId: store.state.userState.tokenUserId, 21 | ); 22 | }, builder: (BuildContext context, _ViewModel vm) { 23 | return _postDialogView(context, vm); 24 | }); 25 | } 26 | 27 | Widget _postDialogView(BuildContext context, _ViewModel vm) { 28 | final BillBloc _bloc = BillBloc(); 29 | 30 | _bloc.postRecurringBill(this.recurringBill, vm.tokenId); 31 | 32 | return StreamBuilder>( 33 | stream: _bloc.billStream, 34 | builder: (context, snapshot) { 35 | if (snapshot.hasData) { 36 | switch (snapshot.data.status) { 37 | case Status.LOADING: 38 | return RichAlertDialog( 39 | alertTitle: richTitle("Cargando"), 40 | alertSubtitle: richSubtitle("Se esta creando el gasto recurrente..."), 41 | alertType: RichAlertType.CUSTOM, 42 | dialogIcon: Icon(Icons.access_time, color: redPrimaryColor,), 43 | ); 44 | break; 45 | 46 | case Status.COMPLETED: 47 | return RichAlertDialog( 48 | alertTitle: richTitle("¡Completado!"), 49 | alertSubtitle: richSubtitle("Gasto recurrente creado correctamente"), 50 | alertType: RichAlertType.SUCCESS, 51 | actions: [ 52 | FlatButton( 53 | child: Text("OK"), 54 | onPressed: () { 55 | Navigator.of(context).pop(); 56 | Navigator.of(context).push( 57 | MaterialPageRoute( 58 | builder: (context) { 59 | return GroupPage(); 60 | }, 61 | ), 62 | ); 63 | }, 64 | ) 65 | ], 66 | ); 67 | break; 68 | case Status.ERROR: 69 | return RichAlertDialog( 70 | alertTitle: richTitle("Error"), 71 | alertSubtitle: richSubtitle("Error creando el gasto recurrente"), 72 | alertType: RichAlertType.ERROR, 73 | actions: [ 74 | FlatButton( 75 | child: Text("OK"), 76 | onPressed: () { 77 | Navigator.pop(context, true); 78 | }, 79 | ) 80 | ], 81 | ); 82 | break; 83 | } 84 | } 85 | return Container(); 86 | }); 87 | } 88 | } 89 | 90 | class _ViewModel { 91 | final String tokenId; 92 | 93 | _ViewModel({@required this.tokenId}); 94 | } -------------------------------------------------------------------------------- /lib/ui/widget/recurring-bills-list.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/model/bill.dart'; 2 | import 'package:daruma/model/group.dart'; 3 | import 'package:daruma/model/recurring-bill.dart'; 4 | import 'package:daruma/redux/index.dart'; 5 | import 'package:daruma/services/bloc/bill.bloc.dart'; 6 | import 'package:daruma/services/networking/response.dart'; 7 | import 'package:daruma/ui/pages/detail-recurring-bill.page.dart'; 8 | import 'package:daruma/util/colors.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_redux/flutter_redux.dart'; 11 | import 'package:google_fonts/google_fonts.dart'; 12 | 13 | class RecurringBillsList extends StatelessWidget { 14 | final List bills; 15 | 16 | RecurringBillsList({this.bills}); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return new StoreConnector( 21 | converter: (store) => _ViewModel( 22 | group: store.state.groupState.group, 23 | tokenId: store.state.userState.tokenUserId), 24 | builder: (BuildContext context, _ViewModel vm) => 25 | _recurrenctBillsListView(context, vm), 26 | ); 27 | } 28 | 29 | Widget _recurrenctBillsListView(BuildContext context, _ViewModel vm) { 30 | final BillBloc _bloc = BillBloc(); 31 | _bloc.getRecurringBills(vm.group.groupId, vm.tokenId); 32 | 33 | return StreamBuilder>>( 34 | stream: _bloc.billStreamRecurringBills, 35 | builder: (context, snapshot) { 36 | if (snapshot.hasData) { 37 | switch (snapshot.data.status) { 38 | case Status.LOADING: 39 | return Center(child: CircularProgressIndicator()); 40 | break; 41 | 42 | case Status.COMPLETED: 43 | return Expanded( 44 | child: ListView.builder( 45 | shrinkWrap: true, 46 | physics: const NeverScrollableScrollPhysics(), 47 | itemCount: snapshot.data.data.length, 48 | itemBuilder: (context, index) { 49 | Bill specificBill; 50 | 51 | for(int i = 0; i(converter: (store) { 17 | return new _ViewModel( 18 | tokenId: store.state.userState.tokenUserId, 19 | ); 20 | }, builder: (BuildContext context, _ViewModel vm) { 21 | return _selectMemberInGroupDialogView(context, vm); 22 | }); 23 | } 24 | 25 | Widget _selectMemberInGroupDialogView(BuildContext context, _ViewModel vm) { 26 | final MemberBloc _bloc = MemberBloc(); 27 | 28 | _bloc.setUserIdToMember(this.memberId, this.userId, vm.tokenId); 29 | 30 | return StreamBuilder>( 31 | stream: _bloc.memberStream, 32 | builder: (context, snapshot) { 33 | if (snapshot.hasData) { 34 | switch (snapshot.data.status) { 35 | case Status.LOADING: 36 | return Center(child: CircularProgressIndicator()); 37 | break; 38 | 39 | case Status.COMPLETED: 40 | return Container( 41 | height: 300.0, // Change as per your requirement 42 | width: 300.0, 43 | child: Column( 44 | mainAxisAlignment: MainAxisAlignment.center, 45 | children: [ 46 | Text("¡Te has identificado correctamente!"), 47 | FlatButton( 48 | onPressed: () { 49 | Navigator.pop(context, true); 50 | Navigator.of(context).push( 51 | MaterialPageRoute( 52 | builder: (context) { 53 | return WelcomeScreen(); 54 | }, 55 | ), 56 | ); 57 | }, 58 | child: Text( 59 | "Salir", 60 | ), 61 | ) 62 | ], 63 | ), 64 | ); 65 | break; 66 | case Status.ERROR: 67 | return Container( 68 | height: 300.0, // Change as per your requirement 69 | width: 300.0, 70 | child: Row( 71 | children: [ 72 | Text("Set Id ERROR!"), 73 | FlatButton( 74 | onPressed: () { 75 | Navigator.pop(context, true); 76 | }, 77 | child: Text( 78 | "Exit", 79 | ), 80 | ) 81 | ], 82 | ), 83 | ); 84 | break; 85 | } 86 | } 87 | return Container(); 88 | }); 89 | } 90 | } 91 | 92 | class _ViewModel { 93 | final String tokenId; 94 | 95 | _ViewModel({ 96 | @required this.tokenId, 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /lib/ui/widget/sign-out-button.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:daruma/util/colors.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class SignOutButton extends StatelessWidget { 5 | final Function() logout; 6 | 7 | SignOutButton({this.logout}); 8 | 9 | Widget build(BuildContext context) { 10 | return RaisedButton( 11 | onPressed: () { 12 | this.logout(); 13 | }, 14 | color: redPrimaryColor, 15 | child: Padding( 16 | padding: const EdgeInsets.all(8.0), 17 | child: Text( 18 | 'Sign Out', 19 | style: TextStyle(fontSize: 25, color: Colors.white), 20 | ), 21 | ), 22 | elevation: 5, 23 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/ui/widget/text-form-field.widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomTextFormField extends StatelessWidget { 4 | final String hintText; 5 | final Function validator; 6 | final Function onSaved; 7 | final String initialValue; 8 | 9 | CustomTextFormField({ 10 | this.hintText, 11 | this.validator, 12 | this.onSaved, 13 | this.initialValue, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return TextFormField( 19 | decoration: InputDecoration( 20 | hintText: hintText, 21 | filled: true, 22 | fillColor: Colors.transparent, 23 | contentPadding: EdgeInsets.all(1.0) 24 | ), 25 | validator: validator, 26 | onSaved: onSaved, 27 | onChanged: onSaved, 28 | initialValue: initialValue, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/util/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const redPrimaryColor = Color.fromRGBO(221, 70, 73, 1); 4 | const white = Colors.white; 5 | const black = Colors.black; 6 | const greenSuccess = Color.fromRGBO(50, 190, 166, 1); 7 | -------------------------------------------------------------------------------- /lib/util/csv.dart: -------------------------------------------------------------------------------- 1 | import 'package:csv/csv.dart'; 2 | import 'package:daruma/model/bill.dart'; 3 | 4 | class CsvHelper { 5 | static CsvHelper _instance; 6 | ListToCsvConverter _listToCsvConverter = ListToCsvConverter(); 7 | 8 | factory CsvHelper() { 9 | if (_instance == null) _instance = CsvHelper._internal(); 10 | return _instance; 11 | } 12 | 13 | CsvHelper._internal(); 14 | 15 | String billsToCsv(List bills) { 16 | List> convertMaterial = [ 17 | [ 18 | 'Nombre', 19 | 'Fecha', 20 | 'Coste', 21 | 'Moneda', 22 | ] 23 | ]; 24 | bills.map((Bill variant) { 25 | List toAdd = []; 26 | toAdd.add(variant.name); 27 | toAdd.add(variant.date); 28 | toAdd.add(variant.money); 29 | toAdd.add(variant.currencyCode); 30 | convertMaterial.add(toAdd); 31 | }).toList(); 32 | return _listToCsvConverter.convert(convertMaterial); 33 | } 34 | } -------------------------------------------------------------------------------- /lib/util/keys.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Keys { 4 | static final navKey = new GlobalKey(); 5 | } 6 | -------------------------------------------------------------------------------- /lib/util/output-file.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path_provider/path_provider.dart'; 4 | 5 | class Output { 6 | static Output _instance; 7 | 8 | factory Output() { 9 | if (_instance == null) _instance = Output._internal(); 10 | return _instance; 11 | } 12 | 13 | Output._internal(); 14 | 15 | Future writeString(String fileName, String text) async { 16 | 17 | if (text == null || fileName == null) return; 18 | 19 | final directory = await getExternalStorageDirectory(); 20 | final pathOfTheFileToWrite = directory.path + "/$fileName.csv"; 21 | 22 | File file = File(pathOfTheFileToWrite); 23 | file.writeAsString(text); 24 | } 25 | } -------------------------------------------------------------------------------- /lib/util/routes.dart: -------------------------------------------------------------------------------- 1 | class Routes { 2 | static final groupPage = "GROUP_PAGE"; 3 | } 4 | -------------------------------------------------------------------------------- /lib/util/url.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_config/flutter_config.dart'; 2 | 3 | class Url { 4 | static String apiBaseUrl = FlutterConfig.get('HOST'); 5 | static String currencyListBaseUrl = 6 | 'https://openexchangerates.org/api/currencies.json'; 7 | } 8 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: daruma 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.2.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | cupertino_icons: ^0.1.2 24 | firebase_auth: ^0.15.4 25 | google_sign_in: ^4.1.1 26 | flutter_facebook_login: ^1.2.0 27 | firebase_database: ^3.1.3 28 | rxdart: 0.23.1 29 | redux: ^4.0.0 30 | flutter_redux: ^0.6.0 31 | redux_thunk: ^0.3.0 32 | google_fonts: ^0.1.0 33 | validators: ^2.0.0+1 34 | money: ^0.2.1 35 | uuid: ^2.0.4 36 | contacts_service: ^0.0.8 37 | permission_handler: 5.0.0+hotfix.3 38 | firebase_dynamic_links: ^0.5.0+11 39 | flutter_custom_dialog: ^1.0.16 40 | toast: ^0.1.5 41 | share: ^0.6.3+6 42 | provider: ^4.0.4 43 | datetime_picker_formfield: ^1.0.0 44 | intl: ^0.16.1 45 | flutter_multi_formatter: ^1.1.5 46 | grouped_buttons: ^1.0.4 47 | money2: ^1.3.0 48 | flutter_staggered_animations: "^0.1.2" 49 | sweet_alert_dialogs: "^0.2.0" 50 | url_launcher: ^5.4.5 51 | csv: ^4.0.3 52 | flutter_file_dialog: ^0.0.5 53 | path_provider: ^1.6.8 54 | avataaar_image: ^1.0.8 55 | 56 | dev_dependencies: 57 | flutter_config: ^1.0.7 58 | flutter_test: 59 | sdk: flutter 60 | 61 | # For information on the generic Dart part of this file, see the 62 | # following page: https://dart.dev/tools/pub/pubspec 63 | 64 | # The following section is specific to Flutter. 65 | flutter: 66 | # The following line ensures that the Material Icons font is 67 | # included with your application, so that you can use the icons in 68 | # the material Icons class. 69 | uses-material-design: true 70 | 71 | # To add assets to your application, add an assets section, like this: 72 | assets: 73 | - assets/ 74 | - .env 75 | # - images/a_dot_ham.jpeg 76 | # An image asset can refer to one or more resolution-specific "variants", see 77 | # https://flutter.dev/assets-and-images/#resolution-aware. 78 | # For details regarding adding assets from package dependencies, see 79 | # https://flutter.dev/assets-and-images/#from-packages 80 | # To add custom fonts to your application, add a fonts section here, 81 | # in this "flutter" section. Each entry in this list should have a 82 | # "family" key with the font family name, and a "fonts" key with a 83 | # list giving the asset and other descriptors for the font. For 84 | # example: 85 | # fonts: 86 | # - family: Schyler 87 | # fonts: 88 | # - asset: fonts/Schyler-Regular.ttf 89 | # - asset: fonts/Schyler-Italic.ttf 90 | # style: italic 91 | # - family: Trajan Pro 92 | # fonts: 93 | # - asset: fonts/TrajanPro.ttf 94 | # - asset: fonts/TrajanPro_Bold.ttf 95 | # weight: 700 96 | # 97 | # For details regarding fonts from package dependencies, 98 | # see https://flutter.dev/custom-fonts/#from-packages 99 | -------------------------------------------------------------------------------- /screenshots/balances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/screenshots/balances.png -------------------------------------------------------------------------------- /screenshots/detail-bill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/screenshots/detail-bill.png -------------------------------------------------------------------------------- /screenshots/groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/screenshots/groups.png -------------------------------------------------------------------------------- /screenshots/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/screenshots/history.png -------------------------------------------------------------------------------- /screenshots/new-bill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantern74/flutter-daruma/ff37fbc77aac772ec26ba1466b7355cf2b23664a/screenshots/new-bill.png -------------------------------------------------------------------------------- /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:daruma/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 | --------------------------------------------------------------------------------