├── .gitignore
├── mobileApp
├── windows
│ ├── flutter
│ │ ├── .template_version
│ │ ├── generated_plugin_registrant.cc
│ │ ├── GeneratedPlugins.props
│ │ └── generated_plugin_registrant.h
│ ├── runner
│ │ ├── resources
│ │ │ └── app_icon.ico
│ │ ├── window_configuration.cpp
│ │ ├── resource.h
│ │ ├── window_configuration.h
│ │ ├── runner.exe.manifest
│ │ ├── flutter_window.cpp
│ │ ├── flutter_window.h
│ │ ├── main.cpp
│ │ ├── run_loop.h
│ │ ├── Runner.rc
│ │ ├── run_loop.cpp
│ │ └── win32_window.h
│ ├── scripts
│ │ ├── prepare_dependencies.bat
│ │ └── bundle_assets_and_deps.bat
│ ├── AppConfiguration.props
│ ├── .gitignore
│ ├── FlutterBuild.vcxproj
│ ├── Runner.sln
│ └── Runner.vcxproj.filters
├── ios
│ ├── Flutter
│ │ ├── .last_build_id
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-50x50@1x.png
│ │ │ │ ├── Icon-App-50x50@2x.png
│ │ │ │ ├── Icon-App-57x57@1x.png
│ │ │ │ ├── Icon-App-57x57@2x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-72x72@1x.png
│ │ │ │ ├── Icon-App-72x72@2x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── GoogleService-Info.plist
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Runner.xcodeproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── .gitignore
│ └── Podfile
├── assets
│ ├── logo.png
│ ├── scribbles
│ │ ├── scribble1.png
│ │ ├── scribble2.png
│ │ ├── scribble3.png
│ │ ├── scribble4.png
│ │ ├── scribble5.png
│ │ ├── scribble6.png
│ │ ├── karlsson_pincode.png
│ │ ├── karlsson_searching.png
│ │ ├── karlsson_contact_page.png
│ │ ├── karlsson_holding_book.png
│ │ ├── karlsson_paper_plane.png
│ │ ├── karlsson_pen_scribble.png
│ │ └── karlsson_no_connection.png
│ └── fonts
│ │ └── Aileron
│ │ ├── Aileron-Bold.otf
│ │ ├── Aileron-Thin.otf
│ │ ├── Aileron-Black.otf
│ │ ├── Aileron-Heavy.otf
│ │ ├── Aileron-Italic.otf
│ │ ├── Aileron-Light.otf
│ │ ├── Aileron-Regular.otf
│ │ ├── Aileron-SemiBold.otf
│ │ ├── Aileron-BlackItalic.otf
│ │ ├── Aileron-BoldItalic.otf
│ │ ├── Aileron-HeavyItalic.otf
│ │ ├── Aileron-LightItalic.otf
│ │ ├── Aileron-ThinItalic.otf
│ │ ├── Aileron-UltraLight.otf
│ │ ├── Aileron-SemiBoldItalic.otf
│ │ └── Aileron-UltraLightItalic.otf
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ └── Icon-512.png
│ ├── manifest.json
│ └── index.html
├── android
│ ├── app
│ │ ├── key.jks
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── values
│ │ │ │ │ │ ├── colors.xml
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ ├── drawable-hdpi
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── drawable-mdpi
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ │ └── ic_launcher.xml
│ │ │ │ │ └── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── dev
│ │ │ │ │ │ └── preetjdp
│ │ │ │ │ │ └── youoweme
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle
│ │ └── google-services.json
│ ├── key.properties
│ ├── gradle.properties
│ ├── .gitignore
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── settings.gradle
│ └── build.gradle
├── lib
│ ├── resources
│ │ ├── graphql
│ │ │ ├── seva.dart
│ │ │ ├── queries
│ │ │ │ ├── getOwe
│ │ │ │ │ ├── getOwe.dart
│ │ │ │ │ └── getOwe.graphql
│ │ │ │ └── me.graphql
│ │ │ ├── coercers.dart
│ │ │ └── youoweme.schema.graphql
│ │ ├── providers.dart
│ │ ├── notifiers
│ │ │ ├── contactProxyNotifier.dart
│ │ │ └── meNotifier.dart
│ │ └── extensions.dart
│ ├── ui
│ │ ├── IntroFlow
│ │ │ ├── authFlow
│ │ │ │ └── authFlow.dart
│ │ │ ├── permissionsFlow
│ │ │ │ ├── permissionsFlow.dart
│ │ │ │ ├── contactPermissions.dart
│ │ │ │ └── notificationsPermission.dart
│ │ │ ├── loginUser.dart
│ │ │ ├── providers.dart
│ │ │ └── introFlow.dart
│ │ ├── Abstractions
│ │ │ ├── yomSpacer.dart
│ │ │ ├── expandingWidgetDelegate.dart
│ │ │ ├── yomSpinner.dart
│ │ │ ├── yomBottomSheet.dart
│ │ │ ├── yomAvatar.dart
│ │ │ └── yomTheme.dart
│ │ ├── NewOwe
│ │ │ └── providers.dart
│ │ ├── IOwe
│ │ │ ├── iOwePageEmptyState.dart
│ │ │ ├── iOwePageElement.dart
│ │ │ └── iOwePageBottomSheet.dart
│ │ ├── OweMe
│ │ │ ├── oweMePageEmptyState.dart
│ │ │ └── oweMePageElement.dart
│ │ └── HomePage
│ │ │ ├── oweMeSection.dart
│ │ │ └── iOweSection.dart
│ └── main.dart
├── test_driver
│ ├── app.dart
│ └── app_test.dart
├── test
│ └── widget_test.dart
├── .metadata
├── build.yaml
├── .gitignore
└── README.md
├── funding.yaml
├── server
├── .gitignore
├── app.yaml
├── src
│ ├── db
│ │ ├── pubSubFire.ts
│ │ └── firebase.ts
│ ├── utils
│ │ ├── appContext.ts
│ │ ├── authChecker.ts
│ │ ├── envConfig.ts
│ │ ├── localFirebaseConfig.ts
│ │ ├── helpers.ts
│ │ └── dynamicLinks.ts
│ ├── modules
│ │ ├── Owe
│ │ │ ├── deleteOwe
│ │ │ │ └── deleteOweInputType.ts
│ │ │ ├── updateOwe
│ │ │ │ └── updateOweInputType.ts
│ │ │ ├── OwesResolver.ts
│ │ │ ├── newOwe
│ │ │ │ └── newOweInputType.ts
│ │ │ ├── oweResolver
│ │ │ │ └── oweSnapshotMap.ts
│ │ │ ├── deleteOweResolver.ts
│ │ │ ├── updateOweResolver.ts
│ │ │ ├── OweResolver.ts
│ │ │ └── NewOweResolver.ts
│ │ └── User
│ │ │ ├── updateUser
│ │ │ └── updateUserInputType.ts
│ │ │ ├── UserSubscriptionResolver.ts
│ │ │ ├── userResolver
│ │ │ ├── userSnapshotMap.ts
│ │ │ ├── userTopic.ts
│ │ │ └── userLoader.ts
│ │ │ ├── MeResolver.ts
│ │ │ └── updateUserResolver.ts
│ ├── schema.ts
│ ├── models
│ │ ├── User.ts
│ │ └── Owe.ts
│ └── index.ts
├── Dockerfile
├── tsconfig.json
├── README.md
└── package.json
├── .github
├── PULL_REQUEST_TEMPLATE
│ └── pr2.md
├── workflows
│ ├── sendLgtm.yaml
│ ├── dependencyBot.yaml
│ ├── serverBuild.yaml
│ └── mobileAppBuild.yaml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── PULL_REQUEST_TEMPLATE.md
├── firebase
├── .firebaserc
├── functions
│ ├── .gitignore
│ ├── src
│ │ ├── index.ts
│ │ ├── db
│ │ │ ├── firebase.ts
│ │ │ └── twilio.ts
│ │ ├── modules
│ │ │ ├── User
│ │ │ │ ├── onNewUser.ts
│ │ │ │ └── onUserDelete.ts
│ │ │ └── Owe
│ │ │ │ └── onNewOwe.ts
│ │ └── utils
│ │ │ ├── helpers.ts
│ │ │ └── dynamicLinks.ts
│ ├── tsconfig.json
│ └── package.json
├── data
│ ├── firestore_export
│ │ ├── all_namespaces
│ │ │ └── all_kinds
│ │ │ │ ├── output-0
│ │ │ │ └── all_namespaces_all_kinds.export_metadata
│ │ └── firestore_export.overall_export_metadata
│ └── firebase-export-metadata.json
├── storage.rules
├── database.rules.json
├── firestore.rules
├── firestore.indexes.json
├── firebase.json
├── .gitignore
└── README.md
├── .devcontainer
├── devcontainer.json
└── Dockerfile
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | mobileApp/pubspec.lock
2 | tasks.md
3 |
--------------------------------------------------------------------------------
/mobileApp/windows/flutter/.template_version:
--------------------------------------------------------------------------------
1 | 2
2 |
--------------------------------------------------------------------------------
/mobileApp/ios/Flutter/.last_build_id:
--------------------------------------------------------------------------------
1 | d943b2753c4a75f5495481e3c1db8615
--------------------------------------------------------------------------------
/funding.yaml:
--------------------------------------------------------------------------------
1 | custom: ["https://devfolio.co/projects/simplefin/cheer"]
2 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
4 | secrets/*
5 |
6 | *.env
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/pr2.md:
--------------------------------------------------------------------------------
1 | ### THis is a test
2 |
3 | Test
4 |
5 | Insert here
--------------------------------------------------------------------------------
/server/app.yaml:
--------------------------------------------------------------------------------
1 | runtime: nodejs
2 | env: flex
3 |
4 | network:
5 | session_affinity: true
--------------------------------------------------------------------------------
/firebase/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "youoweme-6c622"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/mobileApp/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/logo.png
--------------------------------------------------------------------------------
/mobileApp/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/web/favicon.png
--------------------------------------------------------------------------------
/mobileApp/android/app/key.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/key.jks
--------------------------------------------------------------------------------
/mobileApp/android/key.properties:
--------------------------------------------------------------------------------
1 | storePassword=q^iB07QT
2 | keyPassword=q^iB07QT
3 | keyAlias=key
4 | storeFile=key.jks
--------------------------------------------------------------------------------
/mobileApp/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/mobileApp/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/mobileApp/lib/resources/graphql/seva.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | export 'seva.graphql.dart';
3 |
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/scribble1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/scribble1.png
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/scribble2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/scribble2.png
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/scribble3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/scribble3.png
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/scribble4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/scribble4.png
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/scribble5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/scribble5.png
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/scribble6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/scribble6.png
--------------------------------------------------------------------------------
/mobileApp/lib/resources/graphql/queries/getOwe/getOwe.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | export 'getOwe.graphql.dart';
3 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IntroFlow/authFlow/authFlow.dart:
--------------------------------------------------------------------------------
1 | export 'mobilePage.dart';
2 | export 'namePage.dart';
3 | export 'otpPage.dart';
4 |
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-Bold.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-Thin.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-Thin.otf
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/karlsson_pincode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/karlsson_pincode.png
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IntroFlow/permissionsFlow/permissionsFlow.dart:
--------------------------------------------------------------------------------
1 | export 'notificationsPermission.dart';
2 | export 'contactPermissions.dart';
3 |
--------------------------------------------------------------------------------
/mobileApp/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-Black.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-Black.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-Heavy.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-Heavy.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-Italic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-Italic.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-Light.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-Regular.otf
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/karlsson_searching.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/karlsson_searching.png
--------------------------------------------------------------------------------
/mobileApp/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/server/src/db/pubSubFire.ts:
--------------------------------------------------------------------------------
1 | import PubSub from "graphql-firestore-subscriptions"
2 |
3 | const PubSubFire = new PubSub()
4 |
5 | export { PubSubFire }
--------------------------------------------------------------------------------
/mobileApp/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-SemiBold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-SemiBold.otf
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/karlsson_contact_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/karlsson_contact_page.png
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/karlsson_holding_book.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/karlsson_holding_book.png
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/karlsson_paper_plane.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/karlsson_paper_plane.png
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/karlsson_pen_scribble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/karlsson_pen_scribble.png
--------------------------------------------------------------------------------
/mobileApp/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-BlackItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-BlackItalic.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-BoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-BoldItalic.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-HeavyItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-HeavyItalic.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-LightItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-LightItalic.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-ThinItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-ThinItalic.otf
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-UltraLight.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-UltraLight.otf
--------------------------------------------------------------------------------
/mobileApp/assets/scribbles/karlsson_no_connection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/scribbles/karlsson_no_connection.png
--------------------------------------------------------------------------------
/firebase/functions/.gitignore:
--------------------------------------------------------------------------------
1 | ## Compiled JavaScript files
2 | **/*.js
3 | **/*.js.map
4 |
5 | # Typescript v1 declaration files
6 | typings/
7 |
8 | node_modules/
--------------------------------------------------------------------------------
/mobileApp/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-SemiBoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-SemiBoldItalic.otf
--------------------------------------------------------------------------------
/server/src/utils/appContext.ts:
--------------------------------------------------------------------------------
1 | import { Request } from 'express'
2 |
3 | export interface ApplicationContext {
4 | req: Request;
5 | requestId: number
6 | }
--------------------------------------------------------------------------------
/mobileApp/assets/fonts/Aileron/Aileron-UltraLightItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/assets/fonts/Aileron/Aileron-UltraLightItalic.otf
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/firebase/data/firestore_export/all_namespaces/all_kinds/output-0:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/firebase/data/firestore_export/all_namespaces/all_kinds/output-0
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/firebase/storage.rules:
--------------------------------------------------------------------------------
1 | service firebase.storage {
2 | match /b/{bucket}/o {
3 | match /{allPaths=**} {
4 | allow read, write: if request.auth!=null;
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #F1F5F9
4 |
--------------------------------------------------------------------------------
/firebase/data/firebase-export-metadata.json:
--------------------------------------------------------------------------------
1 | {"version":"8.3.0","firestore":{"version":"1.11.3","path":"firestore_export","metadata_file":"firestore_export/firestore_export.overall_export_metadata"}}
--------------------------------------------------------------------------------
/firebase/data/firestore_export/firestore_export.overall_export_metadata:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/firebase/data/firestore_export/firestore_export.overall_export_metadata
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/firebase/database.rules.json:
--------------------------------------------------------------------------------
1 | {
2 | /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
3 | "rules": {
4 | ".read": false,
5 | ".write": false
6 | }
7 | }
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobileApp/windows/scripts/prepare_dependencies.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | :: Run flutter tool backend.
4 | set BUILD_MODE=%~1
5 | "%FLUTTER_ROOT%\packages\flutter_tools\bin\tool_backend" windows-x64 %BUILD_MODE%
6 |
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/mobileApp/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/mobileApp/test_driver/app.dart:
--------------------------------------------------------------------------------
1 | import 'package:YouOweMe/main.dart' as yomApp;
2 | import 'package:flutter_driver/driver_extension.dart';
3 | void main() {
4 | enableFlutterDriverExtension();
5 | yomApp.main();
6 | }
--------------------------------------------------------------------------------
/server/src/modules/Owe/deleteOwe/deleteOweInputType.ts:
--------------------------------------------------------------------------------
1 | import { InputType, Field, ID } from "type-graphql";
2 |
3 | @InputType()
4 | export class DeleteOweInputType {
5 | @Field(() => ID)
6 | id: string
7 | }
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/kotlin/dev/preetjdp/youoweme/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.preetjdp.youoweme
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/firebase/firestore.rules:
--------------------------------------------------------------------------------
1 | rules_version = '2';
2 | service cloud.firestore {
3 | match /databases/{database}/documents {
4 | match /{document=**} {
5 | allow read, write: if request.auth.uid != null
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/mobileApp/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // 📦 Package imports:
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | void main() {
5 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
6 | },skip: true);
7 | }
8 |
--------------------------------------------------------------------------------
/firebase/data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/preetjdp/YouOweMe/HEAD/firebase/data/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata
--------------------------------------------------------------------------------
/mobileApp/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | #include "generated_plugin_registrant.h"
6 |
7 |
8 | void RegisterPlugins(flutter::PluginRegistry* registry) {
9 | }
10 |
--------------------------------------------------------------------------------
/mobileApp/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobileApp/windows/AppConfiguration.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | youoweme
5 |
6 |
7 |
--------------------------------------------------------------------------------
/firebase/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | import { onNewUser } from "./modules/User/onNewUser"
2 | import { onUserDelete } from "./modules/User/onUserDelete"
3 | import { onNewOwe } from "./modules/Owe/onNewOwe"
4 |
5 | export {
6 | onNewUser,
7 | onUserDelete,
8 | onNewOwe
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/mobileApp/lib/resources/graphql/coercers.dart:
--------------------------------------------------------------------------------
1 | DateTime fromGraphQLDateTimeToDartDateTime(int timestamp) {
2 | return DateTime.fromMillisecondsSinceEpoch(timestamp);
3 | }
4 |
5 | int fromDartDateTimeToGraphQLTimestamp(DateTime timestamp) {
6 | return timestamp.millisecondsSinceEpoch;
7 | }
8 |
--------------------------------------------------------------------------------
/mobileApp/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/mobileApp/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/mobileApp/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobileApp/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IntroFlow/loginUser.dart:
--------------------------------------------------------------------------------
1 | class LoginUser {
2 | String userName;
3 |
4 | String verificationCode;
5 |
6 | void addName(String name) {
7 | this.userName = name;
8 | }
9 |
10 | void addVerificationCode(String verificationCode) {
11 | this.verificationCode = verificationCode;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/mobileApp/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobileApp/windows/flutter/GeneratedPlugins.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobileApp/windows/runner/window_configuration.cpp:
--------------------------------------------------------------------------------
1 | #include "window_configuration.h"
2 |
3 | const wchar_t* kFlutterWindowTitle = L"youoweme";
4 | const unsigned int kFlutterWindowOriginX = 10;
5 | const unsigned int kFlutterWindowOriginY = 10;
6 | const unsigned int kFlutterWindowWidth = 800;
7 | const unsigned int kFlutterWindowHeight = 600;
8 |
--------------------------------------------------------------------------------
/mobileApp/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobileApp/.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: 28e15ccc5ef2791d7a4dc13cbb384e95d95814e0
8 | channel: master
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/firebase/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "noImplicitReturns": true,
5 | "noUnusedLocals": true,
6 | "outDir": "lib",
7 | "sourceMap": true,
8 | "strict": true,
9 | "target": "es2017",
10 | "esModuleInterop": true
11 | },
12 | "compileOnSave": true,
13 | "include": [
14 | "src"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/mobileApp/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | #ifndef GENERATED_PLUGIN_REGISTRANT_
6 | #define GENERATED_PLUGIN_REGISTRANT_
7 |
8 | #include
9 |
10 | // Registers Flutter plugins.
11 | void RegisterPlugins(flutter::PluginRegistry* registry);
12 |
13 | #endif // GENERATED_PLUGIN_REGISTRANT_
14 |
--------------------------------------------------------------------------------
/server/src/utils/authChecker.ts:
--------------------------------------------------------------------------------
1 | import { AuthChecker } from "type-graphql";
2 | import { ApplicationContext } from "./appContext";
3 |
4 | export const customAuthChecker: AuthChecker = async (
5 | {
6 | context,
7 | },
8 | ) => {
9 | let userId = context.req.headers.authorization
10 | if (!userId) {
11 | return false
12 | }
13 | return true
14 | };
--------------------------------------------------------------------------------
/mobileApp/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobileApp/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/mobileApp/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.github/workflows/sendLgtm.yaml:
--------------------------------------------------------------------------------
1 | name: Send LGTM reaction
2 | on:
3 | issue_comment:
4 | types: [created]
5 | pull_request_review:
6 | types: [submitted]
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@1.0.0
12 | - uses: micnncim/action-lgtm-reaction@master
13 | env:
14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 |
--------------------------------------------------------------------------------
/mobileApp/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.
--------------------------------------------------------------------------------
/server/src/utils/envConfig.ts:
--------------------------------------------------------------------------------
1 | import { config } from "dotenv"
2 |
3 | if (process.env.NODE_ENV == 'development') {
4 | config()
5 | }
6 |
7 |
8 | const PROJECT_ID = process.env.PROJECT_ID as string
9 | const PRIVATE_KEY = (process.env.PRIVATE_KEY as string).replace(/\\n/g, '\n')
10 | const CLIENT_EMAIL = process.env.CLIENT_EMAIL as string
11 |
12 | export {
13 | PROJECT_ID,
14 | PRIVATE_KEY,
15 | CLIENT_EMAIL
16 | }
17 |
--------------------------------------------------------------------------------
/mobileApp/lib/resources/graphql/queries/getOwe/getOwe.graphql:
--------------------------------------------------------------------------------
1 | query($input: String!){
2 | getOwe(id: $input) {
3 | id
4 | title
5 | amount
6 | state
7 | created
8 | permalink
9 | issuedBy {
10 | id
11 | name
12 | image
13 | mobileNo
14 | created
15 | }
16 | issuedTo {
17 | id
18 | name
19 | image
20 | mobileNo
21 | created
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/server/src/modules/User/updateUser/updateUserInputType.ts:
--------------------------------------------------------------------------------
1 | import { InputType, Field, ID } from "type-graphql";
2 | import { OweState, Owe } from "../../../models/Owe";
3 |
4 | @InputType()
5 | export class UpdateUserInputType {
6 | @Field(() => ID)
7 | id: string;
8 |
9 | @Field({
10 | nullable: true
11 | })
12 | name?: string
13 |
14 | @Field({
15 | nullable: true
16 | })
17 | fcmToken?: string
18 | }
--------------------------------------------------------------------------------
/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | # Create and change to the app directory.
4 | WORKDIR /usr/src/app
5 |
6 | # Copy application dependency manifests to the container image.
7 | COPY package*.json ./
8 |
9 | # Install production dependencies.
10 | RUN npm install
11 |
12 | # Copy Dist to the container image
13 | COPY . ./
14 |
15 | RUN npm run build
16 | # Run the web service on container startup.
17 | CMD ["npm", "run", "start:prod" ]
18 |
19 |
--------------------------------------------------------------------------------
/mobileApp/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 |
--------------------------------------------------------------------------------
/mobileApp/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/server/src/modules/User/UserSubscriptionResolver.ts:
--------------------------------------------------------------------------------
1 | import { Resolver, Subscription, Arg, Root } from "type-graphql";
2 | import { User } from "../../models/User";
3 | import { userTopicGenerator } from "./userResolver/userTopic";
4 |
5 | @Resolver(User)
6 | export class UserSubscriptionResolver {
7 | @Subscription(() => User, {
8 | name: "User",
9 | topics: ({ args }) => userTopicGenerator(args.id)
10 |
11 |
12 | })
13 | getUser(@Arg("id") _id: string, @Root() user: User) {
14 | return user
15 | }
16 | }
--------------------------------------------------------------------------------
/server/src/modules/Owe/updateOwe/updateOweInputType.ts:
--------------------------------------------------------------------------------
1 | import { InputType, Field, Int } from "type-graphql";
2 | import { OweState, Owe } from "../../../models/Owe";
3 |
4 | @InputType()
5 | export class UpdateOweInputType {
6 | @Field()
7 | id: string
8 |
9 | @Field(() => Int,{
10 | nullable: true
11 | })
12 | title?: string
13 |
14 | @Field({
15 | nullable: true
16 | })
17 | amount?: number
18 |
19 | @Field(() => OweState, {
20 | nullable: true
21 | })
22 | state?: OweState
23 | }
--------------------------------------------------------------------------------
/mobileApp/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 |
--------------------------------------------------------------------------------
/mobileApp/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 |
--------------------------------------------------------------------------------
/server/src/db/firebase.ts:
--------------------------------------------------------------------------------
1 | import * as admin from "firebase-admin"
2 | import { PROJECT_ID, CLIENT_EMAIL, PRIVATE_KEY } from '../utils/envConfig'
3 | import { configureForLocalFirebase } from "../utils/localFirebaseConfig"
4 |
5 | admin.initializeApp({
6 | credential: admin.credential.cert({
7 | projectId: PROJECT_ID,
8 | privateKey: PRIVATE_KEY,
9 | clientEmail: CLIENT_EMAIL
10 | }),
11 | databaseURL: "https://youoweme-6c622.firebaseio.com"
12 | })
13 |
14 | const firestore = admin.firestore()
15 | const auth = admin.auth()
16 | configureForLocalFirebase()
17 |
18 | export {
19 | firestore,
20 | auth
21 | }
--------------------------------------------------------------------------------
/mobileApp/lib/resources/providers.dart:
--------------------------------------------------------------------------------
1 | // 📦 Package imports:
2 | import 'package:firebase_auth/firebase_auth.dart';
3 | import 'package:hooks_riverpod/hooks_riverpod.dart';
4 |
5 | // 🌎 Project imports:
6 | import 'package:YouOweMe/resources/notifiers/meNotifier.dart';
7 |
8 | final firebaseUserProvider =
9 | StreamProvider((ref) => FirebaseAuth.instance.onAuthStateChanged);
10 |
11 | final meNotifierProvider = ChangeNotifierProvider((ref) {
12 | MeNotifier meNotifier = MeNotifier();
13 | ref
14 | .read(firebaseUserProvider)
15 | .whenData((value) => meNotifier.onProxyUpdate(value));
16 |
17 | return meNotifier;
18 | });
19 |
--------------------------------------------------------------------------------
/firebase/firestore.indexes.json:
--------------------------------------------------------------------------------
1 | {
2 | "indexes": [],
3 | "fieldOverrides": [
4 | {
5 | "collectionGroup": "owes",
6 | "fieldPath": "issuedToRef",
7 | "indexes": [
8 | {
9 | "order": "ASCENDING",
10 | "queryScope": "COLLECTION"
11 | },
12 | {
13 | "order": "DESCENDING",
14 | "queryScope": "COLLECTION"
15 | },
16 | {
17 | "arrayConfig": "CONTAINS",
18 | "queryScope": "COLLECTION"
19 | },
20 | {
21 | "order": "ASCENDING",
22 | "queryScope": "COLLECTION_GROUP"
23 | }
24 | ]
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/mobileApp/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "YouOweMe",
3 | "short_name": "YouOweMe",
4 | "start_url": ".",
5 | "display": "minimal-ui",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | }
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/firebase/functions/src/db/firebase.ts:
--------------------------------------------------------------------------------
1 | import * as admin from "firebase-admin"
2 |
3 | admin.initializeApp()
4 |
5 | const firestore = admin.firestore()
6 | const auth = admin.auth()
7 | const fcm = admin.messaging()
8 |
9 | interface sendFcmNotificationInterface {
10 | deviceToken: string
11 | title: string
12 | body: string
13 | }
14 |
15 | const sendFcmNotification = ({ deviceToken, title, body }: sendFcmNotificationInterface) => {
16 | return fcm.sendToDevice(deviceToken, {
17 | notification: {
18 | title: title,
19 | body: body
20 | }
21 | })
22 | }
23 |
24 | export {
25 | firestore,
26 | auth,
27 | sendFcmNotification
28 | }
--------------------------------------------------------------------------------
/mobileApp/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 |
--------------------------------------------------------------------------------
/.github/workflows/dependencyBot.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | schedule:
3 | - cron: "* 6 * * 3"
4 | jobs:
5 | test:
6 | name: Flutter Dependency Bot
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions/setup-java@v1
11 | with:
12 | java-version: "12.x"
13 | - uses: subosito/flutter-action@v1
14 | with:
15 | channel: "beta"
16 | - name: Run Package
17 | uses: tianhaoz95/update-flutter-packages@v0.0.1
18 | with:
19 | flutter-project: "./mobileApp"
20 | git-email: "preetjdp123@gmail.com"
21 | git-name: "Preet Parekh"
22 | token: ${{ secrets.GITHUB_TOKEN }}
23 |
--------------------------------------------------------------------------------
/server/src/schema.ts:
--------------------------------------------------------------------------------
1 | import { buildSchema, ResolverData } from "type-graphql"
2 | import "reflect-metadata"
3 |
4 | import { customAuthChecker } from "./utils/authChecker"
5 | import { PubSubFire } from "./db/pubSubFire"
6 | import { ApplicationContext } from "./utils/appContext"
7 | import { Container } from "typedi"
8 |
9 |
10 | const generateSchema = async () => {
11 | return await buildSchema({
12 | resolvers: [__dirname + '/modules/**/*.{ts,js}'],
13 | authChecker: customAuthChecker,
14 | dateScalarMode: "timestamp",
15 | pubSub: PubSubFire,
16 | container: (({ context }: ResolverData) => Container.of(context.requestId))
17 | })
18 | }
19 |
20 | export {
21 | generateSchema
22 | }
--------------------------------------------------------------------------------
/server/src/modules/User/userResolver/userSnapshotMap.ts:
--------------------------------------------------------------------------------
1 | import { DocumentSnapshot, Timestamp } from "@google-cloud/firestore";
2 | import { User } from "../../../models/User";
3 |
4 | type userSnapshotMapType = (snapshot: DocumentSnapshot) => User;
5 |
6 | export const mapUserSnapshot: userSnapshotMapType = snapshot => {
7 | const userData = snapshot.data()
8 | if (!snapshot.exists) {
9 | throw Error("User Does Not Exist")
10 | }
11 | const created: Timestamp = userData!.created
12 | return {
13 | id: snapshot.id,
14 | name: userData!.name,
15 | image: userData!.image,
16 | mobileNo: userData!.mobile_no,
17 | fcmToken: userData!.fcm_token,
18 | created: created.toDate()
19 | }
20 | }
--------------------------------------------------------------------------------
/mobileApp/lib/ui/Abstractions/yomSpacer.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | class YomSpacer extends StatelessWidget {
5 | final double height;
6 | final double width;
7 | YomSpacer({this.height = 0, this.width = 0});
8 | @override
9 | Widget build(BuildContext context) {
10 | return SizedBox(height: height, width: width);
11 | }
12 | }
13 |
14 | class SliverYomSpacer extends StatelessWidget {
15 | final double height;
16 | final double width;
17 | SliverYomSpacer({this.height = 0, this.width = 0});
18 | @override
19 | Widget build(BuildContext context) {
20 | return SliverToBoxAdapter(
21 | child: YomSpacer(
22 | height: height,
23 | width: width,
24 | ),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/firebase/functions/src/db/twilio.ts:
--------------------------------------------------------------------------------
1 | import { config } from "firebase-functions"
2 | import Twilio from "twilio"
3 |
4 | const envConfig = config()
5 | const accountSid = envConfig.twilio.sid; // Your Account SID from www.twilio.com/console
6 | const authToken = envConfig.twilio.token;
7 |
8 | const client = Twilio(accountSid, authToken)
9 |
10 | interface sendMessageInterface {
11 | message: string, mobileNo: string
12 | }
13 | /**
14 | * This function accepts two parameters.
15 | * 1. The Message in String.
16 | * 2. The phone number. Eg => "+919594122345"
17 | */
18 | const sendMessage = ({ message, mobileNo }: sendMessageInterface) => client.messages.create({
19 | from: "+18593502133",
20 | to: mobileNo,
21 | body: message
22 | })
23 |
24 | export {
25 | sendMessage
26 | }
--------------------------------------------------------------------------------
/mobileApp/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.5.0'
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 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project.
4 | title: "[NEW IDEA]"
5 | labels: New Idea
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the idea / solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 | Include Figma references / blog posts here for more context for the converstion.
22 |
--------------------------------------------------------------------------------
/firebase/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "scripts": {
4 | "build": "tsc",
5 | "serve": "npm run build && firebase emulators:start --only functions",
6 | "shell": "npm run build && firebase functions:shell",
7 | "start": "npm run shell",
8 | "deploy": "firebase deploy --only functions",
9 | "logs": "firebase functions:log"
10 | },
11 | "engines": {
12 | "node": "10"
13 | },
14 | "main": "lib/index.js",
15 | "dependencies": {
16 | "@google-cloud/firestore": "^3.8.5",
17 | "axios": "^0.19.2",
18 | "firebase-admin": "^8.12.1",
19 | "firebase-functions": "^3.7.0",
20 | "twilio": "^3.46.0"
21 | },
22 | "devDependencies": {
23 | "typescript": "^3.9.5",
24 | "firebase-functions-test": "^0.2.1"
25 | },
26 | "private": true
27 | }
28 |
--------------------------------------------------------------------------------
/server/src/modules/Owe/OwesResolver.ts:
--------------------------------------------------------------------------------
1 | import { Resolver, Query } from "type-graphql";
2 | import { Owe, OweState } from "../../models/Owe";
3 | import { firestore } from "../../db/firebase";
4 | import { Timestamp, DocumentReference } from "@google-cloud/firestore"
5 | import { getPermalinkFromOwe } from "../../utils/helpers";
6 | import { mapOweSnapshot } from "./oweResolver/oweSnapshotMap";
7 |
8 | @Resolver()
9 | export class OwesResolver {
10 |
11 | @Query(() => [Owe])
12 | async getOwes() {
13 | const owesSnapshot = await firestore.collectionGroup('owes').get()
14 | const owes: Array = await Promise.all(owesSnapshot.docs.map(async (oweF) => {
15 | const owe: Owe = await mapOweSnapshot(oweF);
16 | return owe
17 | }))
18 | return owes
19 | }
20 | }
--------------------------------------------------------------------------------
/server/src/modules/Owe/newOwe/newOweInputType.ts:
--------------------------------------------------------------------------------
1 | import { InputType, Field, Int } from "type-graphql";
2 | import { Length, IsInt, IsPhoneNumber } from "class-validator"
3 |
4 | @InputType()
5 | export class NewOweInputType {
6 | @Field()
7 | @Length(1, 255)
8 | title: string
9 |
10 | @Field(() => Int)
11 | @IsInt()
12 | amount: number
13 |
14 | @Field({ nullable: true })
15 | issuedToID: string
16 |
17 | @IsPhoneNumber("IN")
18 | @Field({
19 | nullable: true,
20 | description: "The mobile number has to be of type string and in such a format `+919594128425` "
21 | }
22 | )
23 | mobileNo: string
24 |
25 | @Field({
26 | nullable: true,
27 | description: "This Field has to be provided with Number."
28 | }
29 | )
30 | displayName: string
31 | }
--------------------------------------------------------------------------------
/mobileApp/build.yaml:
--------------------------------------------------------------------------------
1 | targets:
2 | $default:
3 | sources:
4 | - lib/**
5 | - lib/resources/graphql/youoweme.schema.graphql
6 | builders:
7 | artemis:
8 | options:
9 | schema_mapping:
10 | - schema: lib/resources/graphql/youoweme.schema.graphql
11 | queries_glob: lib/resources/graphql/queries/me.graphql
12 | output: lib/resources/graphql/seva.dart
13 | - schema: lib/resources/graphql/youoweme.schema.graphql
14 | queries_glob: lib/resources/graphql/queries/getOwe/getOwe.graphql
15 | output: lib/resources/graphql/queries/getOwe/getOwe.dart
16 | scalar_mapping:
17 | - custom_parser_import: "package:YouOweMe/resources/graphql/coercers.dart"
18 | graphql_type: Timestamp
19 | dart_type: DateTime
20 |
--------------------------------------------------------------------------------
/mobileApp/windows/runner/window_configuration.h:
--------------------------------------------------------------------------------
1 | #ifndef WINDOW_CONFIGURATION_
2 | #define WINDOW_CONFIGURATION_
3 |
4 | // This is a temporary approach to isolate changes that people are likely to
5 | // make to main.cpp, where the APIs are still in flux. This will reduce the
6 | // need to resolve conflicts or re-create changes slightly differently every
7 | // time the Windows Flutter API surface changes.
8 | //
9 | // Longer term there should be simpler configuration options for common
10 | // customizations like this, without requiring native code changes.
11 |
12 | extern const wchar_t* kFlutterWindowTitle;
13 | extern const unsigned int kFlutterWindowOriginX;
14 | extern const unsigned int kFlutterWindowOriginY;
15 | extern const unsigned int kFlutterWindowWidth;
16 | extern const unsigned int kFlutterWindowHeight;
17 |
18 | #endif // WINDOW_CONFIGURATION_
19 |
--------------------------------------------------------------------------------
/server/src/models/User.ts:
--------------------------------------------------------------------------------
1 | import { ObjectType, Field, ID } from "type-graphql";
2 | import { Owe } from "./Owe";
3 |
4 | @ObjectType()
5 | export class User {
6 | @Field(() => ID)
7 | id: string;
8 |
9 | @Field()
10 | name: string;
11 |
12 | @Field({
13 | nullable: true
14 | })
15 | image: string;
16 |
17 | @Field()
18 | mobileNo: string;
19 |
20 | @Field(() => [Owe])
21 | oweMe?: Array
22 |
23 | @Field()
24 | oweMeAmount?: number
25 |
26 | @Field(() => [Owe])
27 | iOwe?: Array
28 |
29 | @Field()
30 | iOweAmount?: number
31 |
32 | @Field({
33 | description: "The Fcm Token to be used to send a notifiation to the `User` will be null if not present.",
34 | nullable: true
35 | })
36 | fcmToken: string
37 |
38 | @Field()
39 | created: Date
40 | }
--------------------------------------------------------------------------------
/firebase/functions/src/modules/User/onNewUser.ts:
--------------------------------------------------------------------------------
1 | import * as functions from "firebase-functions"
2 | import { FieldValue } from "@google-cloud/firestore"
3 | import { firestore } from "../../db/firebase"
4 |
5 | /*
6 | When a new user is created the system guarentees presenece of two things
7 | 1. displayName
8 | 2. mobileNo
9 | */
10 |
11 | const onNewUser = functions
12 | .auth.user().onCreate(async (user: functions.auth.UserRecord) => {
13 | const userId = user.uid
14 | const userRef = firestore
15 | .collection('users')
16 | .doc(userId)
17 |
18 | await userRef.set({
19 | name: user.displayName!,
20 | mobile_no: user.phoneNumber,
21 | created: FieldValue.serverTimestamp()
22 | }, {
23 | merge: true
24 | })
25 | })
26 |
27 | export { onNewUser }
28 |
29 |
--------------------------------------------------------------------------------
/server/src/modules/User/userResolver/userTopic.ts:
--------------------------------------------------------------------------------
1 | import { PubSubFire } from "../../../db/pubSubFire"
2 | import { Timestamp } from "@google-cloud/firestore"
3 | import { User } from "../../../models/User"
4 | import { firestore } from "../../../db/firebase"
5 | import { mapUserSnapshot } from "./userSnapshotMap"
6 |
7 | const userTopicGenerator = (id: string) => {
8 | try {
9 | PubSubFire.registerHandler(`user_subscription_${id}`.toString(), broadcast => {
10 | return firestore.collection('users').doc(id).onSnapshot((snapshot) => {
11 | const user: User = mapUserSnapshot(snapshot)
12 | broadcast(user)
13 | })
14 | })
15 | } catch (e) {
16 | //If the handler for the user already exists then don't cause error.
17 | }
18 | return `user_subscription_${id}`
19 | }
20 |
21 | export {
22 | userTopicGenerator
23 | }
--------------------------------------------------------------------------------
/mobileApp/lib/resources/graphql/queries/me.graphql:
--------------------------------------------------------------------------------
1 | query {
2 | Me {
3 | id
4 | name
5 | image
6 | oweMeAmount
7 | iOweAmount
8 | mobileNo
9 | created
10 | oweMe {
11 | id
12 | title
13 | amount
14 | state
15 | created
16 | issuedBy {
17 | id
18 | name
19 | image
20 | mobileNo
21 | created
22 | }
23 | issuedTo {
24 | id
25 | name
26 | image
27 | mobileNo
28 | created
29 | }
30 | }
31 | iOwe {
32 | id
33 | title
34 | amount
35 | state
36 | created
37 | issuedBy {
38 | id
39 | name
40 | image
41 | mobileNo
42 | created
43 | }
44 | issuedTo {
45 | id
46 | name
47 | image
48 | mobileNo
49 | created
50 | }
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/mobileApp/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 |
--------------------------------------------------------------------------------
/mobileApp/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | #Secrets
13 | # google-services.json
14 |
15 | # IntelliJ related
16 | *.iml
17 | *.ipr
18 | *.iws
19 | .idea/
20 |
21 | # The .vscode folder contains launch configuration and tasks you configure in
22 | # VS Code which you may wish to be included in version control, so this line
23 | # is commented out by default.
24 | #.vscode/
25 |
26 | # Flutter/Dart/Pub related
27 | **/doc/api/
28 | .dart_tool/
29 | .flutter-plugins
30 | .flutter-plugins-dependencies
31 | .packages
32 | .pub-cache/
33 | .pub/
34 | /build/
35 |
36 | # Web related
37 | lib/generated_plugin_registrant.dart
38 |
39 | # Symbolication related
40 | app.*.symbols
41 |
42 | # Obfuscation related
43 | app.*.map.json
44 |
45 | # Exceptions to above rules.
46 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
47 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Dart",
3 | "build": {
4 | "dockerfile": "Dockerfile",
5 | // Update VARIANT to pick a Dart version
6 | "args": {
7 | "VARIANT": "2"
8 | }
9 | },
10 | // Set *default* container specific settings.json values on container create.
11 | "settings": {
12 | "terminal.integrated.shell.linux": "/bin/bash"
13 | },
14 | // Add the IDs of extensions you want installed when the container is created.
15 | "extensions": [
16 | "dart-code.dart-code"
17 | ]
18 | // Use 'forwardPorts' to make a list of ports inside the container available locally.
19 | // "forwardPorts": [],
20 | // Use 'postCreateCommand' to run commands after the container is created.
21 | // "postCreateCommand": "uname -a",
22 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
23 | // "remoteUser": "vscode"
24 | }
--------------------------------------------------------------------------------
/mobileApp/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/firebase/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "database": {
3 | "rules": "database.rules.json"
4 | },
5 | "firestore": {
6 | "rules": "firestore.rules",
7 | "indexes": "firestore.indexes.json"
8 | },
9 | "functions": {
10 | "predeploy": "npm --prefix \"$RESOURCE_DIR\" run build"
11 | },
12 | "hosting": {
13 | "public": "public",
14 | "ignore": [
15 | "firebase.json",
16 | "**/.*",
17 | "**/node_modules/**"
18 | ],
19 | "rewrites": [
20 | {
21 | "source": "**",
22 | "destination": "/index.html"
23 | }
24 | ]
25 | },
26 | "storage": {
27 | "rules": "storage.rules"
28 | },
29 | "emulators": {
30 | "functions": {
31 | "port": 5001
32 | },
33 | "firestore": {
34 | "port": 8080
35 | },
36 | "database": {
37 | "port": 9000
38 | },
39 | "hosting": {
40 | "port": 5000
41 | },
42 | "pubsub": {
43 | "port": 8085
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/NewOwe/providers.dart:
--------------------------------------------------------------------------------
1 | // 📦 Package imports:
2 | import 'package:contacts_service/contacts_service.dart';
3 | import 'package:hooks_riverpod/hooks_riverpod.dart';
4 | import 'package:sliding_up_panel/sliding_up_panel.dart';
5 | import 'package:state_notifier/state_notifier.dart';
6 |
7 | // 🌎 Project imports:
8 | import 'package:YouOweMe/resources/notifiers/contactProxyNotifier.dart';
9 |
10 | final newOweSlidingPanelControllerProvider = Provider((_) => PanelController());
11 |
12 | final newOweSelectedContactProvider =
13 | StateNotifierProvider((ref) => SelectedContactNotifer());
14 |
15 | final contactsChangeNotifierProvider =
16 | ChangeNotifierProvider((ref) => ContactProxyNotifier());
17 |
18 | class SelectedContactNotifer extends StateNotifier {
19 | SelectedContactNotifer() : super(null);
20 |
21 | void setContact(Contact contact) {
22 | if (contact != state) state = contact;
23 | }
24 |
25 | void clear() {
26 | state = null;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ES2018",
5 | "module": "CommonJS",
6 | "moduleResolution": "node",
7 | "outDir": "./dist",
8 | "sourceMap": true,
9 | "removeComments": true,
10 | "resolveJsonModule": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "noImplicitReturns": true,
14 | "emitDecoratorMetadata": true,
15 | "experimentalDecorators": true,
16 | "strictPropertyInitialization": false,
17 |
18 |
19 | "noUnusedLocals": false,
20 | "noImplicitAny": false,
21 | "noImplicitThis": true,
22 | "noUnusedParameters": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "strictNullChecks": true,
25 | "strictFunctionTypes": true
26 |
27 | },
28 | "compileOnSave": true,
29 | "exclude": [
30 | "node_modules"
31 | ],
32 | "include": [
33 | "./src/**/*.tsx",
34 | "./src/**/*.ts",
35 | "./secrets/"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/Abstractions/expandingWidgetDelegate.dart:
--------------------------------------------------------------------------------
1 | // 🎯 Dart imports:
2 | import 'dart:math' as math;
3 |
4 | // 🐦 Flutter imports:
5 | import 'package:flutter/material.dart';
6 |
7 | class ExpandingWidgetDelegate extends SliverPersistentHeaderDelegate {
8 | ExpandingWidgetDelegate({
9 | @required this.minWidth,
10 | @required this.maxWidth,
11 | @required this.child,
12 | });
13 | final double minWidth;
14 | final double maxWidth;
15 | final Widget child;
16 | @override
17 | double get minExtent => minWidth;
18 | @override
19 | double get maxExtent => math.max(maxWidth, minWidth);
20 | @override
21 | Widget build(
22 | BuildContext context, double shrinkOffset, bool overlapsContent) {
23 | return new SizedBox.expand(child: child);
24 | }
25 |
26 | @override
27 | bool shouldRebuild(ExpandingWidgetDelegate oldDelegate) {
28 | return maxWidth != oldDelegate.maxWidth ||
29 | minWidth != oldDelegate.minWidth ||
30 | child != oldDelegate.child;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/mobileApp/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include "flutter/generated_plugin_registrant.h"
4 |
5 | FlutterWindow::FlutterWindow(RunLoop* run_loop,
6 | const flutter::DartProject& project)
7 | : run_loop_(run_loop), project_(project) {}
8 |
9 | FlutterWindow::~FlutterWindow() {}
10 |
11 | void FlutterWindow::OnCreate() {
12 | Win32Window::OnCreate();
13 |
14 | // The size here is arbitrary since SetChildContent will resize it.
15 | flutter_controller_ =
16 | std::make_unique(100, 100, project_);
17 | RegisterPlugins(flutter_controller_.get());
18 | run_loop_->RegisterFlutterInstance(flutter_controller_.get());
19 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
20 | }
21 |
22 | void FlutterWindow::OnDestroy() {
23 | if (flutter_controller_) {
24 | run_loop_->UnregisterFlutterInstance(flutter_controller_.get());
25 | flutter_controller_ = nullptr;
26 | }
27 |
28 | Win32Window::OnDestroy();
29 | }
30 |
--------------------------------------------------------------------------------
/server/src/utils/localFirebaseConfig.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import { firestore } from "../db/firebase"
3 |
4 | const configureForLocalFirebase = async () => {
5 | const useLocalFirebase = await checkForLocalFirebase()
6 | if (useLocalFirebase) {
7 | firestore.settings({
8 | host: 'localhost',
9 | ssl: false,
10 | port: 8080,
11 | customHeaders: {
12 | "Authorization": "Bearer owner"
13 | }
14 | })
15 | }
16 | }
17 |
18 | const checkForLocalFirebase = async (): Promise => {
19 | try {
20 | const res = await axios.get("http://localhost:8080")
21 | if (res.status == 200) {
22 | console.log("Using Local Firebase")
23 | return true
24 | } else {
25 | throw Error("Status Code not 200")
26 | }
27 | } catch (e) {
28 | console.log("Using Production Firebase")
29 | return false
30 | }
31 | }
32 |
33 | export {
34 | configureForLocalFirebase
35 | }
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/Abstractions/yomSpinner.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class YOMSpinner extends StatelessWidget {
6 | final Brightness brightness;
7 | YOMSpinner({this.brightness = Brightness.light});
8 | @override
9 | Widget build(BuildContext context) {
10 | Color spinnerColor;
11 | if (brightness == Brightness.dark) {
12 | spinnerColor = Colors.white;
13 | } else {
14 | spinnerColor = Theme.of(context).accentColor;
15 | }
16 | TargetPlatform platform = Theme.of(context).platform;
17 | if (platform == TargetPlatform.iOS) {
18 | return Theme(
19 | data: ThemeData(
20 | cupertinoOverrideTheme:
21 | CupertinoThemeData(brightness: brightness)),
22 | child: CupertinoActivityIndicator());
23 | } else {
24 | return CircularProgressIndicator(
25 | strokeWidth: 4,
26 | valueColor: AlwaysStoppedAnimation(spinnerColor));
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # You Owe Me Server
2 |
3 | The Backend Server which acts as a middleware
4 | between the the consumer facing applications
5 | and the services.
6 |
7 | Given Graphql is being used here the appliction is supposed to be self deocumenting,
8 | meaning logic, reasoning for a mutation / query should be in t Graphql.
9 |
10 | > Try out the [seva api.](https://api.youoweme.preetjdp.dev/)
11 |
12 | # Resources
13 | 1. [Ben Awad's Benchmarking GraphQL Node.js Servers](https://www.youtube.com/watch?v=JbV7MCeEPb8)
14 | 2. [Fireship.io Apollo Graphql](https://www.youtube.com/watch?v=8D9XnnjFGMs)
15 |
16 | # Things Being Used
17 | 1. [TypeGraphql](https://github.com/MichalLytek/type-graphql) :
18 | To generate Gql Types from Typescript Types.
19 | 2. Typescript : Beacuse Typescript is amazing.
20 | 3. Firebase : For Firestore and Auth.
21 |
22 | # Get Things Running
23 | 1. Add the Environment Variables as follows
24 | ``` env
25 | PROJECT_ID=
26 | PRIVATE_KEY=""
27 | CLIENT_EMAIL=
28 | ```
29 | 2. Run the Project with
30 | ```bash
31 | // The server will run at Port 4000
32 | npm run start:dev
33 | ```
--------------------------------------------------------------------------------
/mobileApp/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_WINDOW_H_
2 | #define FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include "run_loop.h"
8 | #include "win32_window.h"
9 |
10 | #include
11 |
12 | // A window that does nothing but host a Flutter view.
13 | class FlutterWindow : public Win32Window {
14 | public:
15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a
16 | // Flutter view running |project|.
17 | explicit FlutterWindow(RunLoop* run_loop,
18 | const flutter::DartProject& project);
19 | virtual ~FlutterWindow();
20 |
21 | protected:
22 | // Win32Window:
23 | void OnCreate() override;
24 | void OnDestroy() override;
25 |
26 | private:
27 | // The run loop driving events for this window.
28 | RunLoop* run_loop_;
29 |
30 | // The project to run.
31 | flutter::DartProject project_;
32 |
33 | // The Flutter instance hosted by this window.
34 | std::unique_ptr flutter_controller_;
35 | };
36 |
37 | #endif // FLUTTER_WINDOW_H_
38 |
--------------------------------------------------------------------------------
/server/src/modules/Owe/oweResolver/oweSnapshotMap.ts:
--------------------------------------------------------------------------------
1 | import { DocumentSnapshot, DocumentReference } from "@google-cloud/firestore";
2 | import { Owe, OweState } from "../../../models/Owe";
3 | import { getPermalinkFromOwe } from "../../../utils/helpers";
4 |
5 | type oweSnapshotMapType = (snapshot: DocumentSnapshot) => Promise;
6 |
7 | export const mapOweSnapshot: oweSnapshotMapType = async snapshot => {
8 | if (!snapshot.exists) {
9 | throw Error("Owe Does Not Exist")
10 | }
11 | const oweData = snapshot.data()
12 | const oweDate = oweData!.created
13 | const issuedToRef: DocumentReference = oweData!.issuedToRef
14 | const permalink = await getPermalinkFromOwe(snapshot)
15 | const owe: Owe = {
16 | id: snapshot.id,
17 | documenmentRef: snapshot.ref,
18 | title: oweData!.title,
19 | amount: oweData!.amount,
20 | state: oweData?.state ?? OweState.CREATED,
21 | created: oweDate.toDate(),
22 | issuedByID: snapshot.ref.parent!.parent!.id,
23 | issuedToID: issuedToRef.id,
24 | permalink: permalink
25 | }
26 | return owe
27 | }
--------------------------------------------------------------------------------
/server/src/models/Owe.ts:
--------------------------------------------------------------------------------
1 | import { User } from "./User"
2 | import { ObjectType, Field, ID, registerEnumType, Int } from "type-graphql"
3 | import { DocumentReference } from "@google-cloud/firestore"
4 |
5 | @ObjectType()
6 | class Owe {
7 | @Field(() => ID)
8 | id: string
9 |
10 | documenmentRef: DocumentReference
11 | issuedToID: string
12 | issuedByID: string
13 |
14 | @Field()
15 | title: string
16 |
17 | @Field(() => Int)
18 | amount: number
19 |
20 | @Field(() => OweState)
21 | state: OweState
22 |
23 | @Field()
24 | issuedBy?: User
25 |
26 | @Field()
27 | issuedTo?: User
28 |
29 | @Field()
30 | created: Date
31 |
32 | @Field()
33 | permalink: string
34 | }
35 |
36 | enum OweState {
37 | CREATED = "CREATED",
38 | DECLINED = "DECLINED",
39 | ACKNOWLEDGED = "ACKNOWLEDGED",
40 | PAID = "PAID",
41 | // DELAYED = "DELAYED",
42 | }
43 |
44 | registerEnumType(OweState, {
45 | name: "OweState",
46 | description: "Defines the states that a `Owe` can be In. The default is Opened"
47 | })
48 |
49 | export {
50 | Owe,
51 | OweState
52 | }
--------------------------------------------------------------------------------
/firebase/functions/src/modules/User/onUserDelete.ts:
--------------------------------------------------------------------------------
1 | import * as functions from "firebase-functions"
2 | import { QuerySnapshot } from "@google-cloud/firestore"
3 | import { firestore } from "../../db/firebase"
4 |
5 | /*
6 | Steps Performed When the user is deleted.
7 | 1. Delete All the owes in which the user is present. // THis have to be changed in the future.
8 | 2. Delete the user Document.
9 | */
10 |
11 | const onUserDelete = functions
12 | .auth.user().onDelete(async (user: functions.auth.UserRecord) => {
13 | const userId = user.uid
14 | const userRef = firestore
15 | .collection('users')
16 | .doc(userId)
17 |
18 | // Find all the owes assigned to the user.
19 | const userOwesQuery = firestore
20 | .collectionGroup('owes')
21 | .where('issuedToRef', "==", userRef)
22 |
23 | const owes: QuerySnapshot = await userOwesQuery.get()
24 | owes.forEach(async (owe) => {
25 | await owe.ref.delete()
26 | })
27 |
28 | // Delete the user Document
29 | await userRef.delete()
30 | })
31 |
32 | export { onUserDelete }
33 |
34 |
--------------------------------------------------------------------------------
/mobileApp/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "run_loop.h"
7 | #include "window_configuration.h"
8 |
9 | int APIENTRY wWinMain(_In_ HINSTANCE instance,
10 | _In_opt_ HINSTANCE prev,
11 | _In_ wchar_t* command_line,
12 | _In_ int show_command) {
13 | // Attach to console when present (e.g., 'flutter run') or create a
14 | // new console when running with a debugger.
15 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
16 | ::AllocConsole();
17 | }
18 |
19 | RunLoop run_loop;
20 |
21 | flutter::DartProject project(L"data");
22 | FlutterWindow window(&run_loop, project);
23 | Win32Window::Point origin(kFlutterWindowOriginX, kFlutterWindowOriginY);
24 | Win32Window::Size size(kFlutterWindowWidth, kFlutterWindowHeight);
25 | if (!window.CreateAndShow(kFlutterWindowTitle, origin, size)) {
26 | return EXIT_FAILURE;
27 | }
28 | window.SetQuitOnClose(true);
29 |
30 | run_loop.Run();
31 |
32 | return EXIT_SUCCESS;
33 | }
34 |
--------------------------------------------------------------------------------
/server/src/modules/Owe/deleteOweResolver.ts:
--------------------------------------------------------------------------------
1 | import { Resolver, Mutation, Arg, Ctx } from "type-graphql";
2 | import { Owe } from "../../models/Owe";
3 | import { DeleteOweInputType } from "./deleteOwe/deleteOweInputType";
4 | import { ApplicationContext } from "../../utils/appContext";
5 | import { firestore } from "../../db/firebase";
6 | import { Timestamp } from "@google-cloud/firestore";
7 |
8 | @Resolver(Owe)
9 | export class DeleteOweResolver {
10 | @Mutation(() => Date, {})
11 | async deleteOwe(@Arg("data") data: DeleteOweInputType, @Ctx() context: ApplicationContext) {
12 | const userId = context.req.headers.authorization!;
13 | const userRef = firestore.collection('users').doc(userId)
14 | const oweRef = userRef.collection('owes').doc(data.id)
15 |
16 | const oweSnapshot = await oweRef.get()
17 | if (!oweSnapshot.exists) {
18 | throw `No Owe with the Document ID ${data.id} found for user ${userId}`
19 | }
20 | await oweRef.delete();
21 |
22 | // Used Timestamp because the Date api was too dumb to
23 | // deal with.
24 | // new Date().toISOString()
25 | return Timestamp.now().toDate()
26 | }
27 | }
--------------------------------------------------------------------------------
/mobileApp/windows/runner/run_loop.h:
--------------------------------------------------------------------------------
1 | #ifndef RUN_LOOP_H_
2 | #define RUN_LOOP_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | // A runloop that will service events for Flutter instances as well
10 | // as native messages.
11 | class RunLoop {
12 | public:
13 | RunLoop();
14 | ~RunLoop();
15 |
16 | // Prevent copying
17 | RunLoop(RunLoop const&) = delete;
18 | RunLoop& operator=(RunLoop const&) = delete;
19 |
20 | // Runs the run loop until the application quits.
21 | void Run();
22 |
23 | // Registers the given Flutter instance for event servicing.
24 | void RegisterFlutterInstance(
25 | flutter::FlutterViewController* flutter_instance);
26 |
27 | // Unregisters the given Flutter instance from event servicing.
28 | void UnregisterFlutterInstance(
29 | flutter::FlutterViewController* flutter_instance);
30 |
31 | private:
32 | using TimePoint = std::chrono::steady_clock::time_point;
33 |
34 | // Processes all currently pending messages for registered Flutter instances.
35 | TimePoint ProcessFlutterMessages();
36 |
37 | std::set flutter_instances_;
38 | };
39 |
40 | #endif // RUN_LOOP_H_
41 |
--------------------------------------------------------------------------------
/server/src/modules/Owe/updateOweResolver.ts:
--------------------------------------------------------------------------------
1 | import { Resolver, Mutation, Arg, Authorized } from "type-graphql"
2 | import { Owe } from "../../models/Owe";
3 | import { UpdateOweInputType } from "./updateOwe/updateOweInputType";
4 | import { firestore } from "../../db/firebase";
5 | import { OweResolver } from "../Owe/OweResolver"
6 |
7 | @Resolver(Owe)
8 | export class UpdateOweResolver {
9 | @Authorized()
10 | @Mutation(() => Owe, {
11 | description: "This Mutation gives one the ability to update values of a `Owe`.",
12 | })
13 | async updateOwe(@Arg("data") data: UpdateOweInputType) {
14 | //TODO This Query is very expensive!.
15 | const owes = await firestore.collectionGroup("owes").get()
16 | const filteredOwes = owes.docs.filter(doc => doc.id == data.id)
17 | if (filteredOwes.length == 0) {
18 | throw `No Owes with the Document ID ${data.id} found.`
19 | }
20 | const oweSnapshot = filteredOwes[0]
21 | const oweRef = oweSnapshot.ref
22 | const {id, ...updateData} = data
23 | await oweRef.update({ ...updateData })
24 | const oweResponse = await new OweResolver().getOweFromRef(oweRef)
25 | return oweResponse
26 | }
27 | }
--------------------------------------------------------------------------------
/mobileApp/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | YouOweMe
18 |
19 |
20 |
21 |
24 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IntroFlow/providers.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // 📦 Package imports:
5 | import 'package:firebase_auth/firebase_auth.dart';
6 | import 'package:hooks_riverpod/hooks_riverpod.dart';
7 | import 'package:rxdart/rxdart.dart';
8 |
9 | // 🌎 Project imports:
10 | import 'package:YouOweMe/resources/providers.dart';
11 | import 'package:YouOweMe/ui/IntroFlow/loginUser.dart';
12 |
13 | final introFlowPageControllerProvider =
14 | Provider((ref) => PageController(initialPage: 0));
15 |
16 | final authValidatorProvider = StreamProvider((ref) {
17 | BehaviorSubject screeningSubject = BehaviorSubject.seeded(false);
18 | PageController _pageController = ref.read(introFlowPageControllerProvider);
19 |
20 | _pageController.addListener(() {
21 | if (_pageController.page == 4) {
22 | screeningSubject.add(true);
23 | }
24 | });
25 |
26 | ref.watch(firebaseUserProvider).whenData((user) {
27 | if (user != null && _pageController.page == 0) {
28 | screeningSubject.add(true);
29 | } else {
30 | screeningSubject.add(false);
31 | }
32 | });
33 |
34 | ref.onDispose(() => screeningSubject.close());
35 |
36 | return screeningSubject.stream;
37 | });
38 |
39 | final introFlowUserProvider = Provider((ref) => LoginUser());
40 |
--------------------------------------------------------------------------------
/.github/workflows/serverBuild.yaml:
--------------------------------------------------------------------------------
1 | name: Seva Build
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'server/**'
7 | branches: [ master ]
8 |
9 | # Environment variables available to all jobs and steps in this workflow
10 | env:
11 | PROJECT_ID: ${{ secrets.GCLOUD_PROJECT_ID }}
12 | RUN_REGION: us-central1
13 | SERVICE_NAME: seva
14 |
15 | jobs:
16 | setup-build-deploy:
17 | name: Setup, Build, and Deploy
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - name: Checkout
22 | uses: actions/checkout@v1
23 |
24 | # Setup gcloud CLI
25 | - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
26 | with:
27 | version: '275.0.0'
28 | service_account_email: ${{ secrets.GCLOUD_SA_EMAIL }}
29 | service_account_key: ${{ secrets.GCLOUD_APPLICATION_CREDENTIALS}}
30 |
31 | - name: Gcloud Set up
32 | run: |
33 | gcloud config set project $PROJECT_ID
34 |
35 | - name: Build and Submit
36 | run: |
37 | cd server
38 | gcloud builds submit -t gcr.io/$PROJECT_ID/$SERVICE_NAME:$GITHUB_SHA
39 |
40 | - name: Deploy Image To Cloud Run
41 | run: |
42 | gcloud run deploy $SERVICE_NAME \
43 | --region $RUN_REGION \
44 | --image gcr.io/$PROJECT_ID/$SERVICE_NAME:$GITHUB_SHA \
45 | --platform managed
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "youoweme-server",
3 | "version": "0.0.1",
4 | "description": "The Backend Server which acts as a middlewarebetween the the consumer facing applications and the services.",
5 | "scripts": {
6 | "start": "npm run start:prod",
7 | "start:dev": "ts-node-dev --respawn src/index.ts",
8 | "start:prod": "node dist/index.js",
9 | "build": "tsc",
10 | "gcp-build": "npm run build",
11 | "deploy": "gcloud beta app deploy"
12 | },
13 | "engines": {
14 | "node": ">=10.0.0"
15 | },
16 | "dependencies": {
17 | "@google-cloud/firestore": "^4.2.0",
18 | "apollo-server": "^2.16.1",
19 | "axios": "^0.19.2",
20 | "class-validator": "^0.12.2",
21 | "dataloader": "^2.0.0",
22 | "dotenv": "^8.2.0",
23 | "express": "^4.17.1",
24 | "firebase-admin": "^9.0.0",
25 | "graphql": "^15.3.0",
26 | "graphql-firestore-subscriptions": "^1.0.1",
27 | "reflect-metadata": "^0.1.13",
28 | "type-graphql": "^1.0.0-rc.3",
29 | "typedi": "^0.8.0"
30 | },
31 | "devDependencies": {
32 | "@types/express": "^4.17.7",
33 | "@types/node": "^14.0.27",
34 | "@types/graphql": "^14.5.0",
35 | "ts-node-dev": "^1.0.0-pre.56",
36 | "typescript": "^3.9.7"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IOwe/iOwePageEmptyState.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class IOwePageEmptyState extends StatelessWidget {
6 | @override
7 | Widget build(BuildContext context) {
8 | void goToNewOwe() async {
9 | Navigator.of(context).pushNamed('new_owe_page');
10 | }
11 |
12 | return Padding(
13 | padding: EdgeInsets.all(15),
14 | child: Column(
15 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
16 | crossAxisAlignment: CrossAxisAlignment.start,
17 | children: [
18 | Expanded(child: Container()),
19 | Image.asset("assets/scribbles/scribble2.png"),
20 | Expanded(child: Container()),
21 | SizedBox(
22 | height: 10,
23 | ),
24 | Text(
25 | "Oh oo ...\nThere seem to be no Owes here. 😯",
26 | style: Theme.of(context).textTheme.headline5,
27 | ),
28 | SizedBox(
29 | height: 10,
30 | ),
31 | Container(
32 | height: 60,
33 | child: CupertinoButton(
34 | color: Theme.of(context).accentColor,
35 | child: Text('Add an New Owe'),
36 | onPressed: goToNewOwe),
37 | )
38 | ],
39 | ),
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/OweMe/oweMePageEmptyState.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class OweMePageEmptyState extends StatelessWidget {
6 | @override
7 | Widget build(BuildContext context) {
8 | void goToNewOwe() async {
9 | Navigator.of(context).pushNamed('new_owe_page');
10 | }
11 |
12 | return Padding(
13 | padding: EdgeInsets.all(15),
14 | child: Column(
15 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
16 | crossAxisAlignment: CrossAxisAlignment.start,
17 | children: [
18 | Expanded(child: Container()),
19 | Image.asset("assets/scribbles/scribble5.png"),
20 | Expanded(child: Container()),
21 | SizedBox(
22 | height: 10,
23 | ),
24 | Text(
25 | "Oh oo ...\nThere seem to be no Owes here. 😯",
26 | style: Theme.of(context).textTheme.headline5,
27 | ),
28 | SizedBox(
29 | height: 10,
30 | ),
31 | Container(
32 | height: 60,
33 | child: CupertinoButton(
34 | color: Theme.of(context).accentColor,
35 | child: Text('Add an New Owe'),
36 | onPressed: goToNewOwe),
37 | )
38 | ],
39 | ),
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/server/src/index.ts:
--------------------------------------------------------------------------------
1 | import { ApolloServer } from "apollo-server"
2 | import "./utils/envConfig"
3 |
4 |
5 | import { generateSchema } from "./schema"
6 | import { ApplicationContext } from "./utils/appContext"
7 | import { Container } from "typedi"
8 |
9 | const main = async () => {
10 | const inDevMode = process.env.NODE_ENV == 'development'
11 | const schema = await generateSchema()
12 | const server = new ApolloServer({
13 | schema,
14 | introspection: true,
15 | playground: true,
16 | tracing: inDevMode,
17 | context: ({ req, connection }) => {
18 | const requestId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
19 | console.log('Creating Container', requestId)
20 | let context: ApplicationContext = {
21 | req,
22 | requestId
23 | }
24 | if (connection) {
25 | context = { ...context, ...connection.context }
26 | }
27 | return context
28 | },
29 | plugins: [
30 | {
31 | requestDidStart: () => ({
32 | willSendResponse(requestContext) {
33 | console.log('Disposing Container', requestContext.context.requestId)
34 | Container.reset(requestContext.context.requestId);
35 | },
36 | }),
37 | },
38 | ],
39 | });
40 |
41 | const seva = await server.listen({ port: process.env.PORT })
42 | console.log(`Seva is ready at ${seva.url}`)
43 |
44 | }
45 |
46 | main()
47 |
--------------------------------------------------------------------------------
/firebase/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | firebase-debug.log*
8 |
9 | # Firebase cache
10 | .firebase/
11 |
12 | # Firebase config
13 |
14 | # Uncomment this if you'd like others to create their own Firebase project.
15 | # For a team working on the same Firebase project(s), it is recommended to leave
16 | # it commented so all members can deploy to the same project(s) in .firebaserc.
17 | # .firebaserc
18 |
19 | # Runtime data
20 | pids
21 | *.pid
22 | *.seed
23 | *.pid.lock
24 |
25 | # Directory for instrumented libs generated by jscoverage/JSCover
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 | coverage
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (http://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 |
49 | # Optional npm cache directory
50 | .npm
51 |
52 | # Optional eslint cache
53 | .eslintcache
54 |
55 | # Optional REPL history
56 | .node_repl_history
57 |
58 | # Output of 'npm pack'
59 | *.tgz
60 |
61 | # Yarn Integrity file
62 | .yarn-integrity
63 |
64 | # dotenv environment variables file
65 | .env
66 |
--------------------------------------------------------------------------------
/mobileApp/windows/scripts/bundle_assets_and_deps.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | set FLUTTER_CACHE_DIR=%~1
4 | set BUNDLE_DIR=%~2
5 | set PLUGIN_DIR=%~3
6 | set EXE_NAME=%~4
7 |
8 | set DATA_DIR=%BUNDLE_DIR%data
9 |
10 | if not exist "%DATA_DIR%" call mkdir "%DATA_DIR%"
11 | if %errorlevel% neq 0 exit /b %errorlevel%
12 |
13 | :: Write the executable name to the location expected by the Flutter tool.
14 | echo %EXE_NAME%>"%FLUTTER_CACHE_DIR%exe_filename"
15 |
16 | :: Copy the Flutter assets to the data directory.
17 | set FLUTTER_APP_DIR=%~dp0..\..
18 | set ASSET_DIR_NAME=flutter_assets
19 | set TARGET_ASSET_DIR=%DATA_DIR%\%ASSET_DIR_NAME%
20 | if exist "%TARGET_ASSET_DIR%" call rmdir /s /q "%TARGET_ASSET_DIR%"
21 | if %errorlevel% neq 0 exit /b %errorlevel%
22 | call xcopy /s /e /i /q "%FLUTTER_APP_DIR%\build\%ASSET_DIR_NAME%" "%TARGET_ASSET_DIR%"
23 | if %errorlevel% neq 0 exit /b %errorlevel%
24 |
25 | :: Copy the icudtl.dat file from the Flutter tree to the data directory.
26 | call xcopy /y /d /q "%FLUTTER_CACHE_DIR%icudtl.dat" "%DATA_DIR%"
27 | if %errorlevel% neq 0 exit /b %errorlevel%
28 |
29 | :: Copy the Flutter DLL to the target location.
30 | call xcopy /y /d /q "%FLUTTER_CACHE_DIR%flutter_windows.dll" "%BUNDLE_DIR%"
31 | if %errorlevel% neq 0 exit /b %errorlevel%
32 |
33 | :: Copy any Plugin DLLs to the target location.
34 | if exist "%PLUGIN_DIR%" (
35 | call xcopy /y /d /q "%PLUGIN_DIR%"*.dll "%BUNDLE_DIR%"
36 | if %errorlevel% neq 0 exit /b %errorlevel%
37 | )
38 |
--------------------------------------------------------------------------------
/mobileApp/ios/Runner/GoogleService-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CLIENT_ID
6 | 401368738928-9eavhn0fflpq3pcd2148g2bdtt5o9mlg.apps.googleusercontent.com
7 | REVERSED_CLIENT_ID
8 | com.googleusercontent.apps.401368738928-9eavhn0fflpq3pcd2148g2bdtt5o9mlg
9 | ANDROID_CLIENT_ID
10 | 401368738928-224rt3nfqspq9p61mdibl7gdb6npjjs8.apps.googleusercontent.com
11 | API_KEY
12 | AIzaSyAX-O2MQmMqeu1wziP9wmMR9t_q0X9jUkY
13 | GCM_SENDER_ID
14 | 401368738928
15 | PLIST_VERSION
16 | 1
17 | BUNDLE_ID
18 | dev.preetjdp.youoweme
19 | PROJECT_ID
20 | youoweme-6c622
21 | STORAGE_BUCKET
22 | youoweme-6c622.appspot.com
23 | IS_ADS_ENABLED
24 |
25 | IS_ANALYTICS_ENABLED
26 |
27 | IS_APPINVITE_ENABLED
28 |
29 | IS_GCM_ENABLED
30 |
31 | IS_SIGNIN_ENABLED
32 |
33 | GOOGLE_APP_ID
34 | 1:401368738928:ios:4448990b98e7382a0958b5
35 | DATABASE_URL
36 | https://youoweme-6c622.firebaseio.com
37 |
38 |
--------------------------------------------------------------------------------
/server/src/modules/User/MeResolver.ts:
--------------------------------------------------------------------------------
1 | import { Resolver, Query, Authorized, Ctx, Subscription, Publisher, PubSub, Root, Info } from "type-graphql";
2 | import { ApplicationContext } from "../../utils/appContext";
3 | import { firestore } from "../../db/firebase";
4 | import { User } from "../../models/User";
5 | import { Timestamp } from "@google-cloud/firestore";
6 | import { UserResolver } from "./UserResolver";
7 | import { userTopicGenerator } from "./userResolver/userTopic";
8 | import { RequestContainer, UserDataLoader } from "./userResolver/userLoader";
9 | import { mapUserSnapshot } from "./userResolver/userSnapshotMap";
10 |
11 |
12 | @Resolver()
13 | export class MeResolver {
14 | @Authorized()
15 | @Query(() => User, {
16 | name: "Me"
17 | })
18 | async getMe(@Ctx() context: ApplicationContext, @RequestContainer() userDataLoader: UserDataLoader): Promise {
19 | const userId = context.req.headers.authorization!
20 | const userSnapshot = await userDataLoader.load(userId)
21 | if (!userSnapshot) {
22 | throw Error("User Snapshot from loader is Null in MeResolver")
23 | }
24 | const user = mapUserSnapshot(userSnapshot)
25 | return user
26 | }
27 |
28 | @Subscription(() => User, {
29 | name: "Me",
30 | topics: (context) =>
31 | userTopicGenerator(context.context.authorization)
32 |
33 | })
34 | async getMeSubscription(@Root() user: User) {
35 | return user
36 | }
37 | }
--------------------------------------------------------------------------------
/mobileApp/lib/ui/Abstractions/yomBottomSheet.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/foundation.dart';
4 |
5 | // 📦 Package imports:
6 | import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
7 |
8 | // 🌎 Project imports:
9 | import 'package:YouOweMe/ui/Abstractions/yomTheme.dart';
10 | import 'package:YouOweMe/resources/extensions.dart';
11 |
12 | Future showYomBottomSheet(
13 | {@required BuildContext context, @required ScrollWidgetBuilder builder}) {
14 | YomDesign yomDesign = context.yomDesign;
15 | TargetPlatform platform = Theme.of(context).platform;
16 |
17 | Radius _radius = Radius.circular(15);
18 |
19 | RoundedRectangleBorder _roundedBorder = RoundedRectangleBorder(
20 | borderRadius: BorderRadius.only(topLeft: _radius, topRight: _radius));
21 |
22 | if (platform == TargetPlatform.iOS) {
23 | return showCupertinoModalBottomSheet(
24 | context: context,
25 | builder: builder,
26 | backgroundColor: Colors.white,
27 | topRadius: _radius,
28 | shape: _roundedBorder,
29 | duration: Duration(milliseconds: 400),
30 | animationCurve: yomDesign.yomCurve,
31 | );
32 | } else {
33 | return showMaterialModalBottomSheet(
34 | context: context,
35 | builder: builder,
36 | backgroundColor: Colors.white,
37 | shape: _roundedBorder,
38 | duration: Duration(milliseconds: 400),
39 | animationCurve: yomDesign.yomCurve);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/mobileApp/lib/resources/graphql/youoweme.schema.graphql:
--------------------------------------------------------------------------------
1 | input DeleteOweInputType {
2 | id: ID!
3 | }
4 |
5 | type Mutation {
6 | deleteOwe(data: DeleteOweInputType!): Timestamp!
7 | newOwe(data: NewOweInputType!): Owe!
8 | updateOwe(data: UpdateOweInputType!): Owe!
9 | updateUser(data: UpdateUserInputType!): User!
10 | }
11 |
12 | input NewOweInputType {
13 | title: String!
14 | amount: Int!
15 | issuedToID: String
16 | mobileNo: String
17 | displayName: String
18 | }
19 |
20 | type Owe {
21 | id: ID!
22 | title: String!
23 | amount: Int!
24 | state: OweState!
25 | issuedBy: User!
26 | issuedTo: User!
27 | created: Timestamp!
28 | permalink: String!
29 | }
30 |
31 | enum OweState {
32 | CREATED
33 | DECLINED
34 | ACKNOWLEDGED
35 | PAID
36 | }
37 |
38 | type Query {
39 | getOwe(id: String!): Owe!
40 | getOwes: [Owe!]!
41 | Me: User!
42 | getUsers: [User!]!
43 | getUser(id: String!): User
44 | }
45 |
46 | type Subscription {
47 | Me: User!
48 | User(id: String!): User!
49 | }
50 |
51 | scalar Timestamp
52 |
53 | input UpdateOweInputType {
54 | id: String!
55 | title: Int
56 | amount: Float
57 | state: OweState
58 | }
59 |
60 | input UpdateUserInputType {
61 | id: ID!
62 | name: String
63 | fcmToken: String
64 | }
65 |
66 | type User {
67 | id: ID!
68 | name: String!
69 | image: String
70 | mobileNo: String!
71 | oweMe: [Owe!]!
72 | oweMeAmount: Int!
73 | iOwe: [Owe!]!
74 | iOweAmount: Int!
75 | fcmToken: String
76 | created: Timestamp!
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/firebase/README.md:
--------------------------------------------------------------------------------
1 | # You Owe Me Firebase
2 |
3 | This contains the Firebase configuration used to run YouOweMe.
4 | Most importantly the Firebase Serverless Functions.
5 |
6 | ### Add Environment Variable to Firebase Functions
7 |
8 | ```bash
9 | firebase functions:config:set someservice.key="THE API KEY" someservice.id="THE CLIENT ID"
10 | ```
11 |
12 | ### Check for Environment Variables
13 |
14 | ```bash
15 | firebase functions:config:get
16 | ```
17 |
18 | ### Remove Environment Variables
19 |
20 | ```bash
21 | firebase functions:config:unset key1 key2
22 | ```
23 |
24 | ### Take Backup of Firestore Data
25 |
26 | ```bash
27 | $timestamp = Get-Date -Format "HH-mm MM-dd-yyyy"
28 | gcloud firestore export gs://yom-backup/$timestamp
29 | ```
30 |
31 | References
32 |
33 | - https://medium.com/@BrodaNoel/how-to-backup-firebase-firestore-8be94a66138c
34 |
35 | ### Copy backup to localDisk
36 |
37 | ```bash
38 | gsutil cp -r gs://yom-backup .
39 | ```
40 |
41 | ### Run Firebase Emulator With This Data
42 |
43 | ```bash
44 | firebase emulators:start --import ./data/
45 | ```
46 |
47 | ### Start Firebase-UI with the project
48 |
49 | Make sure you're in the firebase-ui folder
50 |
51 | ```bash
52 | $env:GCLOUD_PROJECT="youoweme-6c622"
53 | $env:FIREBASE_EMULATOR_HUB="localhost:4400"
54 | npm start
55 | ```
56 |
57 | ### Environment Varables Structure
58 |
59 | ```json
60 | {
61 | "secret": {
62 | "web_api_key": "WEB_API_KEY"
63 | },
64 | "twilio": {
65 | "token": "TWILIO_TOKEN",
66 | "sid": "TWILIO_SID"
67 | }
68 | }
69 | ```
70 |
--------------------------------------------------------------------------------
/server/src/modules/User/userResolver/userLoader.ts:
--------------------------------------------------------------------------------
1 | import Container, { Service } from "typedi";
2 | import DataLoader from "dataloader"
3 | import { firestore } from "../../../db/firebase";
4 | import { FieldPath, DocumentSnapshot, DocumentData } from "@google-cloud/firestore";
5 | import { createParamDecorator } from "type-graphql";
6 | import { ApplicationContext } from "../../../utils/appContext";
7 |
8 | @Service()
9 | export class UserDataLoader extends DataLoader {
10 | constructor() {
11 | super(async (ids) => {
12 | console.log("Asked For ID's ", ids)
13 | const usersPromise: Promise>[] = ids.map(async (id) => {
14 | return await firestore.collection('users').doc(id).get()
15 | })
16 | const users = await Promise.all(usersPromise)
17 | console.log("Dataloader Response", users.length)
18 | return ids.map((id) => users.find((user) => user.id === id));
19 | });
20 | }
21 | }
22 |
23 | export function RequestContainer(): ParameterDecorator {
24 | return function (target: Object, propertyName: string | symbol, index: number) {
25 | return createParamDecorator(({ context }) => {
26 | const paramtypes = Reflect.getMetadata('design:paramtypes', target, propertyName);
27 | const requestContainer = Container.of(context.requestId);
28 | return requestContainer.get(paramtypes[index]);
29 | })(target, propertyName, index);
30 | };
31 | }
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # Update VARIANT in devcontainer.json to pick a Dart version
2 | ARG VARIANT=2
3 | FROM google/dart:${VARIANT}
4 |
5 | # Options for setup script
6 | ARG INSTALL_ZSH="true"
7 | ARG UPGRADE_PACKAGES="false"
8 | ARG USERNAME=vscode
9 | ARG USER_UID=1000
10 | ARG USER_GID=$USER_UID
11 |
12 | # Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies
13 | COPY library-scripts/*.sh /tmp/library-scripts/
14 | RUN apt-get update \
15 | && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \
16 | && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
17 |
18 | # Add bin location to path
19 | ENV PUB_CACHE="/usr/local/share/pub-cache"
20 | ENV PATH="${PATH}:${PUB_CACHE}/bin"
21 | RUN mkdir -p ${PUB_CACHE} \
22 | && chown ${USERNAME}:root ${PUB_CACHE} \
23 | && echo "if [ \"\$(stat -c '%U' ${PUB_CACHE})\" != \"${USERNAME}\" ]; then sudo chown -R ${USER_UID}:root ${PUB_CACHE}; fi" \
24 | | tee -a /root/.bashrc /root/.zshrc /home/${USERNAME}/.bashrc >> /home/${USERNAME}/.zshrc
25 |
26 | # Install Flutter
27 | ENV FLUTTER_PATH="/flutter"
28 | RUN git clone https://github.com/flutter/flutter.git
29 | ENV PATH="${PATH}:${FLUTTER_PATH}/bin"
30 | RUN flutter doctor
31 |
32 | # [Optional] Uncomment this section to install additional OS packages.
33 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
34 | # && apt-get -y install --no-install-recommends
35 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/Abstractions/yomAvatar.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class YomAvatar extends StatelessWidget {
6 | final String text;
7 | final double size;
8 | final VoidCallback onPressed;
9 | YomAvatar({this.size = 45, this.text = "PP", this.onPressed});
10 | @override
11 | Widget build(BuildContext context) {
12 | final Color color = Theme.of(context).accentColor;
13 | return CupertinoButton(
14 | minSize: 0,
15 | onPressed: onPressed,
16 | padding: EdgeInsets.all(0),
17 | child: Container(
18 | height: size,
19 | width: size,
20 | decoration: BoxDecoration(
21 | shape: BoxShape.circle,
22 | gradient: LinearGradient(
23 | begin: FractionalOffset.topCenter,
24 | end: FractionalOffset.bottomCenter,
25 | colors: [
26 | color,
27 | Color.fromARGB(
28 | color.alpha,
29 | (color.red + 50).clamp(0, 255) as int,
30 | (color.green + 50).clamp(0, 255) as int,
31 | (color.blue + 50).clamp(0, 255) as int,
32 | ),
33 | ],
34 | ),
35 | ),
36 | padding: const EdgeInsets.all(12.0),
37 | child: Center(
38 | child: Text(
39 | text,
40 | style: const TextStyle(
41 | color: CupertinoColors.white,
42 | fontSize: 13.0,
43 | fontWeight: FontWeight.w500,
44 | ),
45 | ),
46 | ),
47 | ),
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/server/src/utils/helpers.ts:
--------------------------------------------------------------------------------
1 | import { DocumentSnapshot } from "@google-cloud/firestore"
2 | import { generateDynamicLink } from "../utils/dynamicLinks"
3 |
4 | /**
5 | * Steps to be followed here.
6 | * @params DocumentSnapshot for the owe.
7 | * @returns The permalink for the `Owe`
8 | * 1. Check if Permalink exists in the document, if Yes, Return it.
9 | * 2. If No Generate the Url.
10 | * 3. Set that Url in Firestore and return the Url.
11 | */
12 | export const getPermalinkFromOwe = async (snapshot: DocumentSnapshot): Promise => {
13 | const oweData = snapshot.data()!
14 | const oweTitle: string = oweData.title
15 | const oweId = snapshot.id
16 | if (oweData.permalink) {
17 | return oweData.permalink
18 | }
19 | const link = `https://youoweme.preetjdp.dev/owe/${oweId}`
20 | const dynamicLink = await generateDynamicLink({
21 | link: link,
22 | domainUriPrefix: "youoweme.page.link",
23 | androidInfo: {
24 | androidPackageName: "dev.preetjdp.youoweme",
25 | androidFallbackLink: link
26 | },
27 | iosInfo: {
28 | iosBundleId: "dev.preetjdp.youoweme",
29 | iosFallbackLink: link
30 | },
31 | socialMetaTagInfo: {
32 | socialTitle: "Owe Details",
33 | socialDescription: oweTitle,
34 | socialImageLink: "https://firebasestorage.googleapis.com/v0/b/youoweme-6c622.appspot.com/o/statics%2Flogo_small.png?alt=media&token=f42fa2b3-77dd-4afe-9627-7df493e036e9"
35 | },
36 | }, {
37 | option: "SHORT"
38 | })
39 |
40 | snapshot.ref.update({ "permalink": dynamicLink })
41 |
42 | return dynamicLink
43 | }
--------------------------------------------------------------------------------
/firebase/functions/src/utils/helpers.ts:
--------------------------------------------------------------------------------
1 | import { DocumentSnapshot } from "@google-cloud/firestore"
2 | import { generateDynamicLink } from "../utils/dynamicLinks"
3 |
4 | /**
5 | * Steps to be followed here.
6 | * @params DocumentSnapshot for the owe.
7 | * @returns The permalink for the `Owe`
8 | * 1. Check if Permalink exists in the document, if Yes, Return it.
9 | * 2. If No Generate the Url.
10 | * 3. Set that Url in Firestore and return the Url.
11 | */
12 | export const getPermalinkFromOwe = async (snapshot: DocumentSnapshot): Promise => {
13 | const oweData = snapshot.data()!
14 | const oweTitle: string = oweData.title
15 | const oweId = snapshot.id
16 | if (oweData.permalink) {
17 | return oweData.permalink
18 | }
19 | const link = `https://youoweme.preetjdp.dev/owe/${oweId}`
20 | const dynamicLink = await generateDynamicLink({
21 | link: link,
22 | domainUriPrefix: "youoweme.page.link",
23 | androidInfo: {
24 | androidPackageName: "dev.preetjdp.youoweme",
25 | androidFallbackLink: link
26 | },
27 | iosInfo: {
28 | iosBundleId: "dev.preetjdp.youoweme",
29 | iosFallbackLink: link
30 | },
31 | socialMetaTagInfo: {
32 | socialTitle: "Owe Details",
33 | socialDescription: oweTitle,
34 | socialImageLink: "https://firebasestorage.googleapis.com/v0/b/youoweme-6c622.appspot.com/o/statics%2Flogo_small.png?alt=media&token=f42fa2b3-77dd-4afe-9627-7df493e036e9"
35 | },
36 | }, {
37 | option: "SHORT"
38 | })
39 |
40 | snapshot.ref.update({ "permalink": dynamicLink })
41 |
42 | return dynamicLink
43 | }
--------------------------------------------------------------------------------
/mobileApp/lib/resources/notifiers/contactProxyNotifier.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // 📦 Package imports:
5 | import 'package:contacts_service/contacts_service.dart';
6 | import 'package:fuzzy/fuzzy.dart';
7 | import 'package:permission_handler/permission_handler.dart';
8 |
9 | class ContactProxyNotifier extends ChangeNotifier {
10 | final TextEditingController contactEditingController =
11 | TextEditingController();
12 | Iterable contacts;
13 | Iterable staticContacts;
14 |
15 | ContactProxyNotifier() {
16 | contactEditingController.addListener(onTextControllerChanged);
17 | init();
18 | }
19 |
20 | void init() async {
21 | Permission contactPermission = Permission.contacts;
22 | bool isGranted = await contactPermission.isGranted;
23 | if (!isGranted) {
24 | PermissionStatus status = await contactPermission.request();
25 | if (status.isDenied) {
26 | return;
27 | }
28 | }
29 | staticContacts = await ContactsService.getContacts(withThumbnails: false);
30 | contacts = staticContacts;
31 | notifyListeners();
32 | }
33 |
34 | void onTextControllerChanged() {
35 | final fuse = Fuzzy(this.staticContacts.toList(),
36 | options: FuzzyOptions(verbose: true, keys: [
37 | WeightedKey(
38 | name: "displayName",
39 | getter: (contact) => contact.displayName,
40 | weight: 0.8),
41 | ]));
42 | final fuzzySearchResult = fuse.search(this.contactEditingController.text);
43 | final result = fuzzySearchResult.map((e) => e.item);
44 | contacts = result;
45 | notifyListeners();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/mobileApp/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 |
--------------------------------------------------------------------------------
/.github/workflows/mobileAppBuild.yaml:
--------------------------------------------------------------------------------
1 | name: Mobile App Build
2 |
3 | on:
4 | push:
5 | paths:
6 | - 'mobileApp/**'
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | defaults:
13 | run:
14 | shell: bash
15 | working-directory: mobileApp
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: actions/setup-java@v1
19 | with:
20 | java-version: '12.x'
21 | - name: Cache Flutter dependencies
22 | uses: actions/cache@v1
23 | with:
24 | path: /opt/hostedtoolcache/flutter
25 | key: ${{ runner.OS }}-flutter-install-cache
26 | - uses: subosito/flutter-action@v1
27 | with:
28 | channel: 'beta'
29 | - name: Get Dependencies
30 | run: flutter packages get
31 | - name: Run Test's
32 | run: flutter test
33 | - name: Build Flutter App
34 | run: flutter build apk
35 | - name: Upload Artifact
36 | uses: actions/upload-artifact@v2
37 | with:
38 | name: 'Android App'
39 | path: 'mobileApp/build/app/outputs/apk/release/app-release.apk'
40 |
41 | - uses: marvinpinto/action-automatic-releases@latest
42 | with:
43 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
44 | automatic_release_tag: latest
45 | prerelease: true
46 | title: "YouOweMe App Build"
47 | files: |
48 | mobileApp/build/app/outputs/apk/release/app-release.apk
49 |
50 | # - name: Release Build
51 | # uses: softprops/action-gh-release@v1
52 | # if: startsWith(github.ref, 'refs/tags/')
53 | # with:
54 | # files: 'mobileApp/build/app/outputs/apk/release/app-release.apk'
55 | # env:
56 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/server/src/modules/User/updateUserResolver.ts:
--------------------------------------------------------------------------------
1 | import { Resolver, Arg, Mutation, Authorized } from "type-graphql";
2 | import { User } from "../../models/User";
3 | import { UpdateUserInputType } from "./updateUser/updateUserInputType";
4 | import { firestore } from "../../db/firebase";
5 | import { UserResolver } from "./UserResolver"
6 | import { RequestContainer, UserDataLoader } from "./userResolver/userLoader";
7 | import { mapUserSnapshot } from "./userResolver/userSnapshotMap";
8 |
9 | @Resolver(User)
10 | export class UpdateUserResolver {
11 | @Authorized()
12 | @Mutation(() => User, {
13 | description: "This Mutation gives one the ability to update values of a `User`. Currently only supports updating the name and the fcm_token",
14 | })
15 | async updateUser(@Arg("data") data: UpdateUserInputType, @RequestContainer() userDataLoader: UserDataLoader) {
16 | const userId = data.id
17 | const userRef = firestore.collection('users').doc(userId)
18 | try {
19 | // This is a anti-pattern
20 | // Would not recommend for larger systems
21 | // Is done bacause fieldNames are different in server and database
22 | let { id, fcmToken, ...updateData } = data
23 | console.log(updateData)
24 | await userRef.update({
25 | ...(fcmToken && { fcm_token: fcmToken }),
26 | ...updateData
27 | })
28 | let userSnapshot = await userDataLoader.load(userId)
29 | if(!userSnapshot) {
30 | throw Error("User Snapshot from loader is Null in updateResolver")
31 | }
32 | let user = mapUserSnapshot(userSnapshot)
33 | return user
34 | } catch (e) {
35 | throw e
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/mobileApp/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 | #endif // English (United States) resources
58 | /////////////////////////////////////////////////////////////////////////////
59 |
60 |
61 |
62 | #ifndef APSTUDIO_INVOKED
63 | /////////////////////////////////////////////////////////////////////////////
64 | //
65 | // Generated from the TEXTINCLUDE 3 resource.
66 | //
67 |
68 |
69 | /////////////////////////////////////////////////////////////////////////////
70 | #endif // not APSTUDIO_INVOKED
71 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IntroFlow/introFlow.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // 📦 Package imports:
6 | import 'package:flutter_hooks/flutter_hooks.dart';
7 | import 'package:hooks_riverpod/hooks_riverpod.dart';
8 | import 'package:basics/basics.dart';
9 |
10 | // 🌎 Project imports:
11 | import 'package:YouOweMe/ui/IntroFlow/providers.dart';
12 | import 'package:YouOweMe/main.dart';
13 | import 'package:YouOweMe/ui/IntroFlow/authFlow/authFlow.dart';
14 | import 'package:YouOweMe/ui/IntroFlow/permissionsFlow/permissionsFlow.dart';
15 |
16 | class IntroFlow extends HookWidget {
17 | @override
18 | Widget build(BuildContext context) {
19 | final PageController _pageController =
20 | useProvider(introFlowPageControllerProvider);
21 | return useProvider(authValidatorProvider).when(
22 | loading: () => IntroFlowEmptyState(),
23 | error: (_, __) => IntroFlowEmptyState(),
24 | data: (bool snapshot) {
25 | if (snapshot.isNull) return IntroFlowEmptyState();
26 | if (snapshot)
27 | return Intermediate();
28 | else
29 | return Scaffold(
30 | body: PageView(
31 | controller: _pageController,
32 | physics: NeverScrollableScrollPhysics(),
33 | children: [
34 | NamePage(),
35 | MobilePage(),
36 | OtpPage(),
37 | NotificationsPermissions(),
38 | // ContactsPermissions(),
39 | Container()
40 | ],
41 | ),
42 | );
43 | });
44 | }
45 | }
46 |
47 | class IntroFlowEmptyState extends StatelessWidget {
48 | @override
49 | Widget build(BuildContext context) {
50 | return Scaffold(
51 | body: Center(child: Text("Loading")),
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/mobileApp/lib/resources/notifiers/meNotifier.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/foundation.dart';
3 | import 'package:flutter/widgets.dart';
4 |
5 | // 📦 Package imports:
6 | import 'package:firebase_auth/firebase_auth.dart';
7 | import 'package:graphql_flutter/graphql_flutter.dart';
8 | import 'package:basics/basics.dart';
9 |
10 | // 🌎 Project imports:
11 | import 'package:YouOweMe/resources/graphql/seva.dart';
12 | import 'package:YouOweMe/resources/helpers.dart';
13 |
14 | class MeNotifier extends ChangeNotifier {
15 | GraphQLClient graphQLClient;
16 |
17 | Seva$Query$User me;
18 |
19 | void onProxyUpdate(FirebaseUser firebaseUser) async {
20 | if (firebaseUser.isNotNull) {
21 | print("MeNotifier Proxy Update");
22 | graphQLClient = await getGraphqlClient(firebaseUser.uid);
23 | getData();
24 | }
25 | }
26 |
27 | Future refresh() async {
28 | await getData();
29 | }
30 |
31 | Future getData() async {
32 | print("Getting Data");
33 | QueryResult result = await graphQLClient.query(QueryOptions(
34 | documentNode: SevaQuery().document,
35 | fetchPolicy: FetchPolicy.cacheAndNetwork));
36 |
37 | Seva$Query mappedData = Seva$Query.fromJson(result.data);
38 | me = mappedData.Me;
39 | notifyListeners();
40 | return me;
41 | }
42 |
43 | Future updateUser(Map data) async {
44 | if (me.isNull) {
45 | throw Exception("Me is Null Right Now");
46 | }
47 | String updateUserMutation = """
48 | mutation(\$input: UpdateUserInputType!) {
49 | updateUser(data: \$input) {
50 | name
51 | mobileNo
52 | }
53 | }
54 | """;
55 | QueryResult result = await graphQLClient.mutate(
56 | MutationOptions(documentNode: gql(updateUserMutation), variables: {
57 | "input": {"id": me.id, ...data}
58 | }));
59 | refresh();
60 | return result;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/mobileApp/windows/FlutterBuild.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | x64
7 |
8 |
9 | Profile
10 | x64
11 |
12 |
13 | Release
14 | x64
15 |
16 |
17 |
18 | 15.0
19 | {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
20 | Flutter Build
21 | 10.0
22 |
23 |
24 |
25 | v141
26 | v142
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | "$(ProjectDir)scripts\prepare_dependencies" $(Configuration)
41 | Running Flutter backend build
42 | force_to_run_every_time
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/mobileApp/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 | YouOweMe
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleTypeRole
25 | Editor
26 | CFBundleURLSchemes
27 |
28 | com.googleusercontent.apps.401368738928-9eavhn0fflpq3pcd2148g2bdtt5o9mlg
29 |
30 |
31 |
32 | CFBundleVersion
33 | $(FLUTTER_BUILD_NUMBER)
34 | LSRequiresIPhoneOS
35 |
36 | UIBackgroundModes
37 |
38 | remote-notification
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIMainStoryboardFile
43 | Main
44 | UISupportedInterfaceOrientations
45 |
46 | UIInterfaceOrientationPortrait
47 | UIInterfaceOrientationLandscapeLeft
48 | UIInterfaceOrientationLandscapeRight
49 |
50 | UISupportedInterfaceOrientations~ipad
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationPortraitUpsideDown
54 | UIInterfaceOrientationLandscapeLeft
55 | UIInterfaceOrientationLandscapeRight
56 |
57 | NSContactsUsageDescription
58 | This app requires contacts access to function properly.
59 | UIViewControllerBasedStatusBarAppearance
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/firebase/functions/src/modules/Owe/onNewOwe.ts:
--------------------------------------------------------------------------------
1 | import * as functions from "firebase-functions"
2 |
3 | import { DocumentSnapshot, DocumentReference } from "@google-cloud/firestore"
4 | import { sendMessage } from "../../db/twilio"
5 | import { sendFcmNotification } from "../../db/firebase"
6 | import { getPermalinkFromOwe } from "../../utils/helpers"
7 |
8 | /**
9 | * 1. Send the request to the the person IssuedTo
10 | * * This can be Fcm Notifications If the Fcm Id is present.
11 | * * Sms If Not Present.
12 | */
13 |
14 | const onNewOwe = functions.firestore.document('users/{issuedById}/owes/{oweId}')
15 | .onCreate(async (snapshot, context) => {
16 | sendNotificationToOweIssuedTo(snapshot)
17 | })
18 |
19 | const sendNotificationToOweIssuedTo = async (oweSnapshot: DocumentSnapshot) => {
20 | const oweRef = oweSnapshot.ref
21 | const oweData = oweSnapshot.data()
22 |
23 | let oweAmount: number = oweData!.amount
24 |
25 | const issuedByRef = oweRef.parent.parent
26 | let issuedBySnapshot = await issuedByRef!.get()
27 | let issuedByData = issuedBySnapshot.data()
28 | let issuedByName: string = issuedByData!.name
29 |
30 | const issuedToRef: DocumentReference = oweData!.issuedToRef
31 | let issuedToSnapshot = await issuedToRef.get()
32 | let issuedToData = issuedToSnapshot.data()
33 |
34 | let mobileNo: string = issuedToData!.mobile_no
35 | let issuedToName: string = issuedToData!.name
36 | let issuedToFcmToken: string | undefined = issuedToData!.fcm_token
37 |
38 | // let message = `Psst. ${issuedToName} It seems that you owe ${issuedByName} ₹${oweAmount}. Click on the link to accept or decline the transaction.`
39 | let message = `Psst. ${issuedToName} It seems that you owe ${issuedByName} ₹${oweAmount}.`
40 |
41 | if (issuedToFcmToken) {
42 | console.log("Sending FCM Notification")
43 | return await sendFcmNotification({
44 | deviceToken: issuedToFcmToken,
45 | title: "New Owe Alert",
46 | body: message
47 | })
48 | }
49 | console.log("Sending SMS")
50 | const permalink = await getPermalinkFromOwe(oweSnapshot)
51 | message += `\n${permalink}`
52 | return sendMessage({ message, mobileNo })
53 | }
54 |
55 | export {
56 | onNewOwe
57 | }
--------------------------------------------------------------------------------
/mobileApp/windows/Runner.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29709.97
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{5A827760-CF8B-408A-99A3-B6C0AD2271E7}"
7 | ProjectSection(ProjectDependencies) = postProject
8 | {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
9 | EndProjectSection
10 | EndProject
11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Flutter Build", "FlutterBuild.vcxproj", "{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}"
12 | EndProject
13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Flutter Plugins", "Flutter Plugins", "{5C2E738A-1DD3-445A-AAC8-EEB9648DD07C}"
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|x64 = Debug|x64
18 | Profile|x64 = Profile|x64
19 | Release|x64 = Release|x64
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.ActiveCfg = Debug|x64
23 | {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.Build.0 = Debug|x64
24 | {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Profile|x64.ActiveCfg = Profile|x64
25 | {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Profile|x64.Build.0 = Profile|x64
26 | {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.ActiveCfg = Release|x64
27 | {5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.Build.0 = Release|x64
28 | {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.ActiveCfg = Debug|x64
29 | {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.Build.0 = Debug|x64
30 | {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.ActiveCfg = Profile|x64
31 | {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.Build.0 = Profile|x64
32 | {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.ActiveCfg = Release|x64
33 | {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.Build.0 = Release|x64
34 | EndGlobalSection
35 | GlobalSection(SolutionProperties) = preSolution
36 | HideSolutionNode = FALSE
37 | EndGlobalSection
38 | GlobalSection(ExtensibilityGlobals) = postSolution
39 | SolutionGuid = {B8A69CB0-A974-4774-9EBD-1E5EECACD186}
40 | EndGlobalSection
41 | GlobalSection(NestedProjects) = preSolution
42 | EndGlobalSection
43 | EndGlobal
--------------------------------------------------------------------------------
/mobileApp/lib/ui/Abstractions/yomTheme.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class YomDesign {
6 | double fontSizeH1 = 100;
7 | double fontSizeH2 = 80;
8 | double fontSizeH3 = 50;
9 | double fontSizeH4 = 28;
10 | double fontSizeH5 = 20;
11 | double fontSizeH6 = 14;
12 |
13 | Color yomWhite = Color.fromRGBO(241, 245, 249, 1);
14 | Color yomGrey1 = Color.fromRGBO(52, 59, 70, 1);
15 | Color yomGrey2 = Color.fromRGBO(78, 80, 88, 1);
16 |
17 | Curve yomCurve = Curves.easeInOutQuad;
18 | }
19 |
20 | ThemeData yomTheme() {
21 | YomDesign yomDesign = YomDesign();
22 | return ThemeData(
23 | fontFamily: "Aileron",
24 | scaffoldBackgroundColor: yomDesign.yomWhite,
25 | backgroundColor: yomDesign.yomWhite,
26 | accentColor: yomDesign.yomGrey1,
27 | cupertinoOverrideTheme:
28 | CupertinoThemeData(primaryColor: yomDesign.yomGrey1),
29 | floatingActionButtonTheme: FloatingActionButtonThemeData(
30 | backgroundColor: yomDesign.yomGrey1,
31 | elevation: 2,
32 | ),
33 | textTheme: TextTheme(
34 | headline1: TextStyle(
35 | fontSize: yomDesign.fontSizeH1,
36 | fontWeight: FontWeight.bold,
37 | color: yomDesign.yomGrey2),
38 | headline2: TextStyle(
39 | fontSize: yomDesign.fontSizeH2,
40 | fontWeight: FontWeight.bold,
41 | color: yomDesign.yomGrey2),
42 | headline3: TextStyle(
43 | fontSize: yomDesign.fontSizeH3,
44 | fontWeight: FontWeight.bold,
45 | color: yomDesign.yomGrey2),
46 | headline4: TextStyle(
47 | fontSize: yomDesign.fontSizeH4,
48 | fontWeight: FontWeight.bold,
49 | color: yomDesign.yomGrey2),
50 | headline5: TextStyle(
51 | fontSize: yomDesign.fontSizeH5,
52 | fontWeight: FontWeight.bold,
53 | color: yomDesign.yomGrey2),
54 | headline6: TextStyle(
55 | fontSize: yomDesign.fontSizeH6,
56 | fontWeight: FontWeight.bold,
57 | color: yomDesign.yomGrey2),
58 | bodyText2: TextStyle(
59 | fontSize: 18,
60 | fontWeight: FontWeight.normal,
61 | color: yomDesign.yomGrey2),
62 | ));
63 | }
64 |
--------------------------------------------------------------------------------
/server/src/utils/dynamicLinks.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 |
3 | /**
4 | * @params link The link your app will open.
5 | * @params domainUriPrefix The URL Prefix for the app.
6 | * @params androidParameters The Android Parameters for the URL.
7 | * @params iosParameters The IOS Parameters for the URL.
8 | * @params navigationInfoParameters The Navigation Parameters for the URL.
9 | * @params suffix Should the end url be short or long
10 | */
11 | type DynamicLinkParameters = {
12 | link: string
13 | domainUriPrefix: string
14 | androidInfo: AndroidParameters
15 | iosInfo: IosParameters
16 | socialMetaTagInfo: SocialMetaTagParameters
17 | navigationInfo?: NavigationInfoParameters
18 | }
19 |
20 | type AndroidParameters = {
21 | /** The package name of the Android app to use to open the link. */
22 | androidPackageName: string
23 | /** The link to open when the app isn't installed. */
24 | androidFallbackLink?: string
25 | }
26 |
27 | type IosParameters = {
28 | /** The bundle ID of the iOS app to use to open the link. */
29 | iosBundleId: string,
30 | /** The link to open when the app isn't installed. */
31 | iosFallbackLink?: string
32 | }
33 |
34 | type NavigationInfoParameters = {
35 | /** If set to '1', skip the app preview page when the Dynamic Link is opened, and instead redirect to the app or store. */
36 | enableForcedRedirect: boolean
37 | }
38 |
39 | type SocialMetaTagParameters = {
40 | /** The title to use when the Dynamic Link is shared in a social post. */
41 | socialTitle: string
42 | /** The description to use when the Dynamic Link is shared in a social post. */
43 | socialDescription?: string
44 | /** The URL to an image related to this link. The image should be at least 300x200 px, and less than 300 KB.*/
45 | socialImageLink?: string
46 | }
47 |
48 | type SuffixParameter = {
49 | option: "SHORT" | "UNGUESSABLE"
50 | }
51 |
52 |
53 | export const generateDynamicLink = async (parameters: DynamicLinkParameters, suffix: SuffixParameter): Promise => {
54 | const api_key = "AIzaSyC9mDgysM-vQlguS3X52Kx42sOmrDtKVAY"
55 | const requestBody = {
56 | dynamicLinkInfo: parameters,
57 | suffix
58 | }
59 | const response = await axios.post(`https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=${api_key}`, requestBody)
60 | return response.data.shortLink
61 | }
--------------------------------------------------------------------------------
/mobileApp/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 keystoreProperties = new Properties()
10 | def keystorePropertiesFile = rootProject.file('key.properties')
11 | if (keystorePropertiesFile.exists()) {
12 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
13 | }
14 |
15 | def flutterRoot = localProperties.getProperty('flutter.sdk')
16 | if (flutterRoot == null) {
17 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
18 | }
19 |
20 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
21 | if (flutterVersionCode == null) {
22 | flutterVersionCode = '1'
23 | }
24 |
25 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
26 | if (flutterVersionName == null) {
27 | flutterVersionName = '1.0'
28 | }
29 |
30 | apply plugin: 'com.android.application'
31 | apply plugin: 'kotlin-android'
32 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
33 | apply plugin: 'com.google.gms.google-services'
34 |
35 | android {
36 | compileSdkVersion 28
37 |
38 | sourceSets {
39 | main.java.srcDirs += 'src/main/kotlin'
40 | }
41 |
42 | lintOptions {
43 | disable 'InvalidPackage'
44 | }
45 |
46 | defaultConfig {
47 | applicationId "dev.preetjdp.youoweme"
48 | minSdkVersion 16
49 | targetSdkVersion 28
50 | versionCode flutterVersionCode.toInteger()
51 | versionName flutterVersionName
52 | multiDexEnabled true
53 | }
54 |
55 | signingConfigs {
56 | release {
57 | keyAlias keystoreProperties['keyAlias']
58 | keyPassword keystoreProperties['keyPassword']
59 | storeFile file(keystoreProperties['storeFile'])
60 | storePassword keystoreProperties['storePassword']
61 | }
62 | }
63 |
64 | buildTypes {
65 | release {
66 | signingConfig signingConfigs.release
67 | }
68 | }
69 | }
70 |
71 | flutter {
72 | source '../..'
73 | }
74 |
75 | dependencies {
76 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
77 | }
78 |
--------------------------------------------------------------------------------
/firebase/functions/src/utils/dynamicLinks.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import { config } from "firebase-functions"
3 |
4 | /**
5 | * @params link The link your app will open.
6 | * @params domainUriPrefix The URL Prefix for the app.
7 | * @params androidParameters The Android Parameters for the URL.
8 | * @params iosParameters The IOS Parameters for the URL.
9 | * @params navigationInfoParameters The Navigation Parameters for the URL.
10 | * @params suffix Should the end url be short or long
11 | */
12 | type DynamicLinkParameters = {
13 | link: string
14 | domainUriPrefix: string
15 | androidInfo: AndroidParameters
16 | iosInfo: IosParameters
17 | socialMetaTagInfo: SocialMetaTagParameters
18 | navigationInfo?: NavigationInfoParameters
19 | }
20 |
21 | type AndroidParameters = {
22 | /** The package name of the Android app to use to open the link. */
23 | androidPackageName: string
24 | /** The link to open when the app isn't installed. */
25 | androidFallbackLink?: string
26 | }
27 |
28 | type IosParameters = {
29 | /** The bundle ID of the iOS app to use to open the link. */
30 | iosBundleId: string,
31 | /** The link to open when the app isn't installed. */
32 | iosFallbackLink?: string
33 | }
34 |
35 | type NavigationInfoParameters = {
36 | /** If set to '1', skip the app preview page when the Dynamic Link is opened, and instead redirect to the app or store. */
37 | enableForcedRedirect: boolean
38 | }
39 |
40 | type SocialMetaTagParameters = {
41 | /** The title to use when the Dynamic Link is shared in a social post. */
42 | socialTitle: string
43 | /** The description to use when the Dynamic Link is shared in a social post. */
44 | socialDescription?: string
45 | /** The URL to an image related to this link. The image should be at least 300x200 px, and less than 300 KB.*/
46 | socialImageLink?: string
47 | }
48 |
49 | type SuffixParameter = {
50 | option: "SHORT" | "UNGUESSABLE"
51 | }
52 |
53 |
54 | export const generateDynamicLink = async (parameters: DynamicLinkParameters, suffix: SuffixParameter): Promise => {
55 | const api_key = config().secret.web_api_key
56 | const requestBody = {
57 | dynamicLinkInfo: parameters,
58 | suffix
59 | }
60 | const response = await axios.post(`https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=${api_key}`, requestBody)
61 | return response.data.shortLink
62 | }
--------------------------------------------------------------------------------
/mobileApp/lib/main.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | // 📦 Package imports:
7 | import 'package:device_preview/device_preview.dart';
8 | import 'package:firebase_analytics/firebase_analytics.dart';
9 | // import 'package:cloud_firestore/cloud_firestore.dart';
10 | // import 'package:firebase/firebase.dart';
11 | import 'package:firebase_core/firebase_core.dart';
12 | import 'package:firebase_analytics/observer.dart';
13 | import 'package:hooks_riverpod/hooks_riverpod.dart';
14 | import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
15 |
16 | // 🌎 Project imports:
17 | import 'package:YouOweMe/ui/Abstractions/yomTheme.dart';
18 | import 'package:YouOweMe/ui/IntroFlow/introFlow.dart';
19 | import 'package:YouOweMe/resources/helpers.dart';
20 |
21 | Future main() async {
22 | WidgetsFlutterBinding.ensureInitialized();
23 | await Firebase.initializeApp();
24 | final preferences = await StreamingSharedPreferences.instance;
25 | configureSystemChrome();
26 | runApp(ProviderScope(
27 | child: MyApp(
28 | preferences: preferences,
29 | ),
30 | ));
31 | }
32 |
33 | class MyApp extends StatelessWidget {
34 | final StreamingSharedPreferences preferences;
35 | MyApp({@required this.preferences});
36 | @override
37 | Widget build(BuildContext context) {
38 | return PreferenceBuilder(
39 | preference:
40 | preferences.getBool('showDevicePreview', defaultValue: false),
41 | builder: (context, shouldShowDevicePreview) {
42 | return DevicePreview(
43 | enabled: shouldShowDevicePreview,
44 | builder: (BuildContext context) => MaterialApp(
45 | title: 'You Owe Me',
46 | builder: DevicePreview.appBuilder,
47 | theme: yomTheme(),
48 | home: IntroFlow()),
49 | );
50 | });
51 | }
52 | }
53 |
54 | class Intermediate extends StatelessWidget {
55 | final FirebaseAnalytics firebaseAnalytics = FirebaseAnalytics();
56 | @override
57 | Widget build(BuildContext context) {
58 | return MaterialApp(
59 | title: 'You Owe Me',
60 | builder: DevicePreview.appBuilder,
61 | theme: yomTheme(),
62 | navigatorObservers: [
63 | FirebaseAnalyticsObserver(analytics: firebaseAnalytics)
64 | ],
65 | onGenerateRoute: routeGenerator);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/mobileApp/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 |
--------------------------------------------------------------------------------
/mobileApp/windows/runner/run_loop.cpp:
--------------------------------------------------------------------------------
1 | #include "run_loop.h"
2 |
3 | #include
4 | // Don't stomp std::min/std::max
5 | #undef max
6 | #undef min
7 |
8 | #include
9 |
10 | RunLoop::RunLoop() {}
11 |
12 | RunLoop::~RunLoop() {}
13 |
14 | void RunLoop::Run() {
15 | bool keep_running = true;
16 | TimePoint next_flutter_event_time = TimePoint::clock::now();
17 | while (keep_running) {
18 | std::chrono::nanoseconds wait_duration =
19 | std::max(std::chrono::nanoseconds(0),
20 | next_flutter_event_time - TimePoint::clock::now());
21 | ::MsgWaitForMultipleObjects(
22 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000),
23 | QS_ALLINPUT);
24 | bool processed_events = false;
25 | MSG message;
26 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects
27 | // won't return again for items left in the queue after PeekMessage.
28 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
29 | processed_events = true;
30 | if (message.message == WM_QUIT) {
31 | keep_running = false;
32 | break;
33 | }
34 | ::TranslateMessage(&message);
35 | ::DispatchMessage(&message);
36 | // Allow Flutter to process messages each time a Windows message is
37 | // processed, to prevent starvation.
38 | next_flutter_event_time =
39 | std::min(next_flutter_event_time, ProcessFlutterMessages());
40 | }
41 | // If the PeekMessage loop didn't run, process Flutter messages.
42 | if (!processed_events) {
43 | next_flutter_event_time =
44 | std::min(next_flutter_event_time, ProcessFlutterMessages());
45 | }
46 | }
47 | }
48 |
49 | void RunLoop::RegisterFlutterInstance(
50 | flutter::FlutterViewController* flutter_instance) {
51 | flutter_instances_.insert(flutter_instance);
52 | }
53 |
54 | void RunLoop::UnregisterFlutterInstance(
55 | flutter::FlutterViewController* flutter_instance) {
56 | flutter_instances_.erase(flutter_instance);
57 | }
58 |
59 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
60 | TimePoint next_event_time = TimePoint::max();
61 | for (auto flutter_controller : flutter_instances_) {
62 | std::chrono::nanoseconds wait_duration =
63 | flutter_controller->ProcessMessages();
64 | if (wait_duration != std::chrono::nanoseconds::max()) {
65 | next_event_time =
66 | std::min(next_event_time, TimePoint::clock::now() + wait_duration);
67 | }
68 | }
69 | return next_event_time;
70 | }
71 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IOwe/iOwePageElement.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // 🌎 Project imports:
6 | import 'package:YouOweMe/ui/Abstractions/yomBottomSheet.dart';
7 | import 'package:YouOweMe/resources/graphql/seva.dart';
8 | import 'package:YouOweMe/ui/Abstractions/yomAvatar.dart';
9 | import 'package:YouOweMe/ui/IOwe/iOwePageBottomSheet.dart';
10 | import 'package:YouOweMe/resources/extensions.dart';
11 |
12 | class IOwePageElement extends StatelessWidget {
13 | final Seva$Query$User$Owe owe;
14 | IOwePageElement({@required this.owe});
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | void showOweDetails() {
19 | Widget builder(BuildContext context, ScrollController scrollController) =>
20 | IOwePageBottomSheet(
21 | scrollController: scrollController,
22 | owe: owe,
23 | );
24 | showYomBottomSheet(context: context, builder: builder);
25 | }
26 |
27 | return GestureDetector(
28 | onTap: showOweDetails,
29 | child: Container(
30 | margin: EdgeInsets.only(top: 10),
31 | constraints: BoxConstraints(minHeight: 50),
32 | child: Row(
33 | crossAxisAlignment: CrossAxisAlignment.center,
34 | children: [
35 | YomAvatar(
36 | text: owe.issuedBy.shortName,
37 | ),
38 | SizedBox(
39 | width: 20,
40 | ),
41 | Expanded(
42 | child: Text(
43 | owe.title,
44 | style: Theme.of(context).textTheme.headline5,
45 | maxLines: 2,
46 | overflow: TextOverflow.ellipsis,
47 | ),
48 | ),
49 | SizedBox(
50 | width: 10,
51 | ),
52 | Container(
53 | constraints: BoxConstraints(minWidth: 65),
54 | child: CupertinoButton(
55 | color: Theme.of(context).accentColor,
56 | minSize: 20,
57 | padding: EdgeInsets.all(10),
58 | child: Text(
59 | owe.amount.toString(),
60 | style: Theme.of(context)
61 | .textTheme
62 | .headline5
63 | .copyWith(color: Colors.white),
64 | ),
65 | onPressed: () {}),
66 | )
67 | ],
68 | ),
69 | ),
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/mobileApp/lib/resources/extensions.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // 📦 Package imports:
5 | import 'package:contacts_service/contacts_service.dart';
6 |
7 | // 🌎 Project imports:
8 | import 'package:YouOweMe/ui/Abstractions/yomTheme.dart';
9 | import 'package:YouOweMe/resources/helpers.dart';
10 | import './graphql/seva.dart';
11 |
12 | extension ListUtils on Iterable {
13 | num sumBy(num f(T element)) {
14 | num sum = 0;
15 | for (var item in this) {
16 | sum += f(item);
17 | }
18 | return sum;
19 | }
20 | }
21 |
22 | extension MeUtils on Seva$Query$User {
23 | String get shortName =>
24 | this.name.split(" ").take(2).map((e) => e[0]).toList().join();
25 | }
26 |
27 | extension OweUtils on List {
28 | List get stateCreated =>
29 | this.where((element) => element.state == OweState.CREATED).toList();
30 |
31 | List fromStates(List states) =>
32 | this.where((element) => states.contains(element.state)).toList();
33 |
34 | List get statePaid =>
35 | this.where((element) => element.state == OweState.PAID).toList();
36 |
37 | List get stateAcknowledged =>
38 | this.where((element) => element.state == OweState.ACKNOWLEDGED).toList();
39 |
40 | int get total =>
41 | this.map((e) => e.amount).reduce((value, element) => value + element);
42 | }
43 |
44 | extension DateUtils on DateTime {
45 | String get simpler {
46 | const monthNames = [
47 | "January",
48 | "February",
49 | "March",
50 | "April",
51 | "May",
52 | "June",
53 | "July",
54 | "August",
55 | "September",
56 | "October",
57 | "November",
58 | "December"
59 | ];
60 | const dayNames = [
61 | "Sunday",
62 | "Monday",
63 | "Tuesday",
64 | "Wednesday",
65 | "Thursday",
66 | "Friday",
67 | "Saturday",
68 | "Sunday",
69 | ];
70 |
71 | DateTime date = this;
72 | return "${dayNames[date.weekday]}, ${monthNames[date.month - 1]} the ${date.day}${getDayOfMonthSuffix(date.day)}";
73 | }
74 | }
75 |
76 | extension MeUtils2 on Seva$Query$User$Owe$User {
77 | String get shortName =>
78 | this.name.split(" ").take(2).map((e) => e[0]).toList().join();
79 | }
80 |
81 | extension ContactUtils on Contact {
82 | String get shortName =>
83 | this.displayName.split(" ").take(2).map((e) => e[0]).toList().join();
84 | }
85 |
86 | extension YomContext on BuildContext {
87 | YomDesign get yomDesign => YomDesign();
88 | }
89 |
--------------------------------------------------------------------------------
/server/src/modules/Owe/OweResolver.ts:
--------------------------------------------------------------------------------
1 | import { Resolver, FieldResolver, Root, Query, Arg, } from "type-graphql";
2 | import { Owe, OweState } from "../../models/Owe";
3 | import { User } from "../../models/User";
4 | import { UserResolver } from "../User/UserResolver";
5 | import { DocumentReference } from "@google-cloud/firestore"
6 | import { getPermalinkFromOwe } from "../../utils/helpers"
7 | import { RequestContainer, UserDataLoader } from "../User/userResolver/userLoader";
8 | import { mapUserSnapshot } from "../User/userResolver/userSnapshotMap";
9 | import { mapOweSnapshot } from "./oweResolver/oweSnapshotMap";
10 | import { firestore } from "../../db/firebase";
11 |
12 | @Resolver(Owe)
13 | export class OweResolver {
14 | async getOweFromRef(oweRef: DocumentReference): Promise {
15 | const oweSnapshot = await oweRef.get()
16 | const owe: Owe = await mapOweSnapshot(oweSnapshot);
17 | return owe
18 | }
19 |
20 | @Query(() => Owe, { name: "getOwe" },)
21 | async getOweFromId(@Arg("id") id: string): Promise {
22 | const owes = await firestore.collectionGroup("owes").get()
23 | const filteredOwes = owes.docs.filter(doc => doc.id == id)
24 | if (filteredOwes.length == 0) {
25 | throw `No Owe with the Document ID ${id} found.`
26 | }
27 | const oweSnapshot = filteredOwes[0]
28 | const owe: Owe = await mapOweSnapshot(oweSnapshot)
29 | return owe
30 | }
31 |
32 | @FieldResolver(() => User, {
33 | name: "issuedBy",
34 | })
35 | async issuedByFieldResolver(@Root() owe: Owe, @RequestContainer() userDataLoader: UserDataLoader) {
36 | const oweRef = owe.documenmentRef
37 | const issuedByRef = oweRef.parent.parent
38 | const issedByUserId = issuedByRef!.id
39 | const issuedByUserSnapshot = await userDataLoader.load(issedByUserId)
40 | if (!issuedByUserSnapshot) {
41 | throw Error("User Snapshot from loader is Null in oweResolver")
42 | }
43 | const issuedByUser = mapUserSnapshot(issuedByUserSnapshot)
44 | return issuedByUser
45 | }
46 |
47 | @FieldResolver(() => User, {
48 | name: "issuedTo",
49 | })
50 | async issuedToFieldResolver(@Root() owe: Owe, @RequestContainer() userDataLoader: UserDataLoader) {
51 | const userId: string = owe.issuedToID
52 | const issuedToUserSnapshot = await userDataLoader.load(userId)
53 | if (!issuedToUserSnapshot) {
54 | throw Error("User Snapshot from loader is Null in oweResolver")
55 | }
56 | const issuedToUser = mapUserSnapshot(issuedToUserSnapshot)
57 | return issuedToUser
58 | }
59 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # You Owe Me
2 |
3 | An Application that keeps tracks of your chillars,
4 | shows off the best ways [1](#Citations) to
5 | build a set of services that come together in a beautiful
6 | structure.
7 |
8 | # Status
9 | Currenly in Active Prototypical Development.
10 |
11 | 
12 |
13 | 
14 |
15 | # Design
16 | The Figma for the project can be found through the link below.
17 |
18 | [Figma Design](https://www.figma.com/file/LCWzdDGz5Uq1X7i0WiNof5/Untitled?node-id=0%3A1 "The Link to the Figma")
19 |
20 | Contact the Admin for editor access.
21 |
22 |
24 |
25 | # Contribution Guide
26 | This mono repo is open source, so feel free to take it apart, and go bonkers
27 | with it 🐱🏍.
28 |
29 | Each service of the product is nicely seperated into its
30 | own folders as a effect of the mono-repo paradigm.
31 |
32 | But if you wish for your idea to land on the app, submit a proposal in the form of
33 | an [Issue](https://github.com/preetjdp/YouOweMe/issues/new?assignees=&labels=New+Idea&template=feature_request.md&title=%5BNEW+IDEA%5D)
34 | and lets percolate on it.
35 |
36 | # Tech Stack
37 | These are the set of services currently being used for the product.
38 | * [Flutter - Mobile App](https://github.com/preetjdp/YouOweMe/tree/master/mobileApp "Mobile App Section")
39 | * [Firebase - Database and Serverless Functions](https://github.com/preetjdp/YouOweMe/tree/master/firebase)
40 | > Download the Insider Release [from here.](https://github.com/preetjdp/YouOweMe/releases)
41 | * [Graphql - Seva](https://github.com/preetjdp/YouOweMe/tree/master/server)
42 | > Try out the [seva api.](https://api.youoweme.preetjdp.dev/)
43 | * WIP
44 |
45 | # Navigating the Codebase
46 | The Codebase is a single
47 | [monorepo](https://www.atlassian.com/git/tutorials/monorepos "What Are MonoRepo's")
48 | consisting of all the infrastructure required to run the services, nicely organised
49 | and placed in its own folders with kick ass documentaion [2](#Citations) .
50 |
51 |
52 | # Code Of Conduct
53 | This project is very much established on the foundation of No Bureaucracy,
54 | No Bullshit, If you have a idea don't supress it down (Let's the ideas flow, like a river).
55 |
56 |
57 | 
58 |
59 | If you're pumped to work with people and have a profound belief
60 | in building amazing products for consumers with empathy,
61 | You're Most Sincerely Welcome 🙏 (नमस्ते).
62 |
63 | # Citations
64 | 1. The best is extremly subjective 😃.
65 | 2. Well Not yet.
--------------------------------------------------------------------------------
/mobileApp/test_driver/app_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter_driver/flutter_driver.dart';
4 | import 'package:test/test.dart';
5 |
6 | void main() {
7 | group("Entire App Test", () {
8 | FlutterDriver driver;
9 |
10 | setUpAll(() async {
11 | // https://github.com/flutter/flutter/issues/12561#issuecomment-448999726
12 | await Process.run(
13 | "adb",
14 | "shell pm grant dev.preetjdp.youoweme android.permission.READ_CONTACTS"
15 | .split(" "));
16 | driver = await FlutterDriver.connect();
17 | await driver.startTracing();
18 | });
19 |
20 | tearDownAll(() async {
21 | Timeline timeline = await driver.stopTracingAndDownloadTimeline();
22 | final summary = new TimelineSummary.summarize(timeline);
23 | print("WOWZA" + summary.toString());
24 | summary.writeSummaryToFile('riverpod',
25 | pretty: true, destinationDirectory: "./test_driver/result/");
26 | if (driver != null) driver.close();
27 | });
28 |
29 | test("Check Flutter Driver health", () async {
30 | Health health = await driver.checkHealth();
31 | print(health.status);
32 | });
33 |
34 | test("Intro Flow", () async {
35 | await driver.tap(find.byType("TextField"));
36 | await driver.enterText("Preet Parekh");
37 | await driver.tap(find.text("Next"));
38 |
39 | await driver.tap(find.byType("TextField"));
40 | await driver.enterText("9594128425");
41 | await driver.tap(find.text("Next"));
42 |
43 | await driver.waitFor(find.byType("PinCodeTextField"));
44 | await driver.tap(find.byType("PinCodeTextField"));
45 | await driver.enterText("123456");
46 |
47 | await driver.waitFor(find.byValueKey("notification_permission_next"));
48 | await driver.tap(find.byValueKey("notification_permission_next"));
49 |
50 | // await driver.waitFor(find.byValueKey("contact_permission_next"));
51 | // await driver.tap(find.byValueKey("contact_permission_next"));
52 |
53 | sleep(const Duration(seconds: 3));
54 | });
55 |
56 | test("OweMe Page", () async {
57 | await driver.tap(find.text("Owe Me"));
58 | await driver.scroll(find.byType("CustomScrollView"), 0, -600,
59 | Duration(milliseconds: 200));
60 |
61 | await driver.tap(find.pageBack());
62 | });
63 |
64 | test("IOwe Page", () async {
65 | await driver.tap(find.text("I Owe"));
66 | await driver.scroll(find.byType("CustomScrollView"), 0, -600,
67 | Duration(milliseconds: 200));
68 |
69 | await driver.tap(find.pageBack());
70 | });
71 |
72 | test("NewOwe Page", () async {
73 | await driver.tap(find.text("New"));
74 |
75 | await Future.delayed(Duration(seconds: 2));
76 |
77 | await driver.tap(find.pageBack());
78 | });
79 | });
80 | }
81 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/OweMe/oweMePageElement.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // 🌎 Project imports:
6 | import 'package:YouOweMe/ui/Abstractions/yomBottomSheet.dart';
7 | import 'package:YouOweMe/resources/graphql/seva.dart';
8 | import 'package:YouOweMe/ui/Abstractions/yomAvatar.dart';
9 | import 'package:YouOweMe/ui/OweMe/oweMePageBottomSheet.dart';
10 | import 'package:YouOweMe/resources/extensions.dart';
11 |
12 | class OweMePageElement extends StatelessWidget {
13 | final Seva$Query$User$Owe owe;
14 | OweMePageElement({@required this.owe});
15 | @override
16 | Widget build(BuildContext context) {
17 | void showOweDetails() async {
18 | Widget builder(BuildContext context, ScrollController scrollController) =>
19 | OweMePageBottomSheet(
20 | scrollController: scrollController,
21 | owe: owe,
22 | );
23 | showYomBottomSheet(context: context, builder: builder);
24 | }
25 |
26 | return GestureDetector(
27 | onTap: showOweDetails,
28 | child: Container(
29 | margin: EdgeInsets.only(top: 10),
30 | constraints: BoxConstraints(minHeight: 50),
31 | child: Row(
32 | crossAxisAlignment: CrossAxisAlignment.center,
33 | children: [
34 | YomAvatar(
35 | text: owe.issuedTo.shortName,
36 | ),
37 | SizedBox(
38 | width: 20,
39 | ),
40 | Expanded(
41 | child: Text(
42 | owe.title,
43 | style: Theme.of(context).textTheme.headline5,
44 | maxLines: 2,
45 | overflow: TextOverflow.ellipsis,
46 | ),
47 | ),
48 | // CupertinoButton(
49 | // onPressed: () {},
50 | // padding: EdgeInsets.symmetric(vertical: 2, horizontal: 10),
51 | // child: Icon(
52 | // CupertinoIcons.check_mark_circled,
53 | // size: 28,
54 | // ),
55 | // ),
56 | SizedBox(
57 | width: 10,
58 | ),
59 | Container(
60 | constraints: BoxConstraints(minWidth: 65),
61 | child: CupertinoButton(
62 | color: Theme.of(context).accentColor,
63 | minSize: 20,
64 | padding: EdgeInsets.all(10),
65 | child: Text(
66 | owe.amount.toString(),
67 | style: Theme.of(context)
68 | .textTheme
69 | .headline5
70 | .copyWith(color: Colors.white),
71 | ),
72 | onPressed: () {}),
73 | )
74 | ],
75 | ),
76 | ),
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/mobileApp/android/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "401368738928",
4 | "firebase_url": "https://youoweme-6c622.firebaseio.com",
5 | "project_id": "youoweme-6c622",
6 | "storage_bucket": "youoweme-6c622.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:401368738928:android:547df5812c0305cc0958b5",
12 | "android_client_info": {
13 | "package_name": "dev.preetjdp.youoweme"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "401368738928-otf7mn29qmv3ctk51ogth53ltgn7hf47.apps.googleusercontent.com",
19 | "client_type": 1,
20 | "android_info": {
21 | "package_name": "dev.preetjdp.youoweme",
22 | "certificate_hash": "7b07940e2eb99be5290b99421bd0363cd5438b30"
23 | }
24 | },
25 | {
26 | "client_id": "401368738928-224rt3nfqspq9p61mdibl7gdb6npjjs8.apps.googleusercontent.com",
27 | "client_type": 1,
28 | "android_info": {
29 | "package_name": "dev.preetjdp.youoweme",
30 | "certificate_hash": "f9d0996a913080be00599af384342b5092a0108e"
31 | }
32 | },
33 | {
34 | "client_id": "401368738928-abmkcd6ulatb39tbn2nm7b8ntk0h8kpe.apps.googleusercontent.com",
35 | "client_type": 1,
36 | "android_info": {
37 | "package_name": "dev.preetjdp.youoweme",
38 | "certificate_hash": "0f17a8693a25871038f5653a95a7e7f316c27ba9"
39 | }
40 | },
41 | {
42 | "client_id": "401368738928-k4v46nvvhm67qtess1anuggqu5f3ocn9.apps.googleusercontent.com",
43 | "client_type": 1,
44 | "android_info": {
45 | "package_name": "dev.preetjdp.youoweme",
46 | "certificate_hash": "35d6edcc540c37e843e3131572c7d24eba082c3b"
47 | }
48 | },
49 | {
50 | "client_id": "401368738928-e11u3pg7v2ngrv7fvo3m242361ih5n8v.apps.googleusercontent.com",
51 | "client_type": 3
52 | }
53 | ],
54 | "api_key": [
55 | {
56 | "current_key": "AIzaSyBWK6dGIPu0CVrm96T4eOsFLH2mgJt4Uf0"
57 | }
58 | ],
59 | "services": {
60 | "appinvite_service": {
61 | "other_platform_oauth_client": [
62 | {
63 | "client_id": "401368738928-e11u3pg7v2ngrv7fvo3m242361ih5n8v.apps.googleusercontent.com",
64 | "client_type": 3
65 | },
66 | {
67 | "client_id": "401368738928-9eavhn0fflpq3pcd2148g2bdtt5o9mlg.apps.googleusercontent.com",
68 | "client_type": 2,
69 | "ios_info": {
70 | "bundle_id": "dev.preetjdp.youoweme"
71 | }
72 | }
73 | ]
74 | }
75 | }
76 | }
77 | ],
78 | "configuration_version": "1"
79 | }
--------------------------------------------------------------------------------
/mobileApp/README.md:
--------------------------------------------------------------------------------
1 | ### Styles
2 |
3 | Dart File Naming Convention => lowerCamelCase
4 |
5 | ### Font
6 |
7 | The font being used throughout the application is
8 | [Aileron Black](https://open-foundry.com/fonts/aileron_black)
9 |
10 | ### How to get the app started.
11 |
12 | - Setup a Firebase Project for your own and use that `google-services.json`
13 |
14 | OR
15 |
16 | - Ask one of the contributors to add your SHA-1 Key to the Firebase Console.
17 |
18 | > _[How to get the SHA-1 Key ?](https://stackoverflow.com/questions/15727912/sha-1-fingerprint-of-keystore-certificate)_
19 |
20 | - Run the app.
21 |
22 | ```bash
23 | flutter run
24 | ```
25 |
26 | ### Startup
27 |
28 | YouOweMe uses [Device Preview](https://pub.dev/packages/device_preview) to
29 | build and to visualise how the app will look
30 | on different devices.
31 |
32 | On First Debug Run you will find a such a layout:
33 |
34 | 
35 |
36 | This applicaiton used Artemis for graphql code generation.
37 |
38 | To update the schema get the latest form the [API](https://youoweme-6c622.appspot.com/),
39 | and update the file located at `lib/resources/graphql/youoweme.schema.graphql`
40 |
41 | ### Code Generator For Artemis.
42 |
43 | When you update / add a new GraphQl Query run this to generate dart
44 | types
45 |
46 | ```bash
47 | flutter pub run build_runner build
48 | ```
49 |
50 | ### Run Import Sorter
51 |
52 | ```bash
53 | flutter pub run import_sorter:main
54 | ```
55 |
56 | ### Add New Queries / Mutation / Subscriptions
57 |
58 | Add them to their respective folders under
59 |
60 | `lib/resources/graphql/`
61 |
62 | ### Add missing files in code.
63 |
64 | Run this very carefully so it does not mess up some
65 | OS level configuration.
66 |
67 | ```bash
68 | flutter create . --org dev.preetjdp youoweme
69 | ```
70 |
71 | ### How to update the icon
72 |
73 | ```bash
74 | flutter pub run flutter_launcher_icons:main
75 | ```
76 |
77 | ### KeyTool options
78 |
79 | `Password => q^iB07QT`
80 |
81 | ### Get Sha Key from key.jks
82 |
83 | ```bash
84 | keytool -list -v -keystore {filePath} -alias {key-alias}
85 | Example:
86 | keytool -list -v -keystore ./android/app/key.jks -alias key
87 |
88 | # Location for Debug Key in Windows
89 | # C:\Users\Dell\.android
90 | ```
91 |
92 | ### Generate New Sha Key
93 |
94 | ```bash
95 | keytool -genkey -v -keystore key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key
96 |
97 | # https://flutter.dev/docs/deployment/android
98 | ```
99 |
100 | ### Launch url on the phone.
101 |
102 | ```bash
103 | adb shell am start -a android.intent.action.VIEW -d https://youoweme.page.link/oNL2
104 | ```
105 |
106 | ### Run Driver Test
107 |
108 | ```bash
109 | flutter drive --target=test_driver/app.dart --profile
110 | ```
111 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | 👉 New release for the YouOweMe
4 |
5 |
6 |
7 | ## 🚀 Updates
8 |
9 | @user [Linear]() | # | [Docs]()
12 | - @user [Linear]() | # | [Docs]()
13 | - @user [Linear]() | # | [Docs]()
14 | - Added an API to fetch the `track` for a Project @utkarsh-var [Linear](https://linear.app/devfolio/issue/BE-412/datalayer-api-for-fetching-the-project-tracks) | #554
15 | - Bring in support for event-based notifications to the Datalayer, this will be used to power the email sent notifications flow for the organizer dashboard @iamazhar @utkarsh-var [Linear](https://linear.app/devfolio/issue/BE-260/notification-data-layer-logic) | #500
16 | - This is powered by Amazon SNS
17 | - Added an API to create a new Notification Topic `POST /notifications/`
18 | - Added an API to subscribe a user to a Topic `POST /notifications/subscribe`
19 | - Added an API to register the device using FCM Token `POST /notifications/register_device`
20 |
21 | ## 🐛Bug Fixes
22 |
23 | - Add better email validation to allow only valid emails to be used @iamazhar [Linear](https://linear.app/devfolio/issue/BE-305/failed-to-send-critical-email) | #515 | [Sentry](https://sentry.io/organizations/hack-inout-tech-llp/issues/2116662182/?referrer=Linear)
24 |
25 | ## 🚄Optimizations
26 |
27 | - Introduce caching to the users' extras API @preetjdp [Linear](https://linear.app/devfolio/issue/BE-317/users-extra-api-datalayer-changes) | #517
28 |
29 | ## 🧹Housekeeping
30 |
31 | - Added more type information, documentation, and overall refactoring to some services and controllers @devfolioco/backend
32 | - Refactored user extra services and controllers @preetjdp #517
33 | - Remove `send_grid` dependencies, services, interfaces that are no longer being used @iamazhar @utkarsh-var #495
34 | - Reactor some bits of the user hackathon services to follow the updated code style @utkarsh-var #495
35 |
36 | ## 🧑🏼💻Developer Info
37 |
38 | - Added a new shared generic type for Pagination [Reference](https://github.com/devfolioco/projectx/blob/d8f5ab478ed8b2a90c543b65e48af9f14ef4f08a/src/shared/types.ts#L23-L36)
39 |
40 | ## 🧪Testing
41 |
42 | - Bring in the CI to run the email service tests using [Localstack](https://www.notion.so/devfolio/Release-Notes-Backend-3dc804bf920c4ab9994e2b64ecce4355) @devfolioco/backend [Linear](https://linear.app/devfolio/issue/BE-368/add-ses-tests) | #540
43 | - Added tests for the user extra services @preetjdp #517
44 | - Added tests for the refactored CSV flow @pushkar-anand #536
45 |
46 | ## Cross service updates
47 |
48 | - Use the updated internal APIs in devfolio API [Release Notes](https://github.com/devfolioco/devfolio-api/releases/tag/v2.0.0)
49 |
50 | ## 🔐 Environment Keys
51 |
52 | - Added `The key that was added`
53 | - Removed `The key that was removed`
54 |
55 | ## Migrations
56 |
57 | - Add a `sns_arns` table
58 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/HomePage/oweMeSection.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // 📦 Package imports:
6 | import 'package:flutter_hooks/flutter_hooks.dart';
7 | import 'package:hooks_riverpod/hooks_riverpod.dart';
8 |
9 | // 🌎 Project imports:
10 | import 'package:YouOweMe/resources/providers.dart';
11 | import 'package:YouOweMe/resources/graphql/seva.dart';
12 | import 'package:YouOweMe/ui/Abstractions/yomSpinner.dart';
13 |
14 | class OweMeSection extends HookWidget {
15 | @override
16 | Widget build(BuildContext context) {
17 | Seva$Query$User me = useProvider(meNotifierProvider).me;
18 |
19 | void goToOweMePage() {
20 | Navigator.of(context).pushNamed('owe_me_page');
21 | }
22 |
23 | return Container(
24 | height: 130,
25 | color: Colors.transparent,
26 | child: Stack(
27 | alignment: Alignment.bottomCenter,
28 | children: [
29 | Positioned(
30 | left: 0,
31 | top: 0,
32 | child: CupertinoButton(
33 | onPressed: goToOweMePage,
34 | minSize: 0,
35 | padding: EdgeInsets.all(0),
36 | child: Row(
37 | children: [
38 | Text("Owe Me",
39 | style: Theme.of(context).textTheme.headline5),
40 | Icon(
41 | CupertinoIcons.right_chevron,
42 | color: Color.fromRGBO(78, 80, 88, 1),
43 | )
44 | ],
45 | ),
46 | )),
47 | Positioned(
48 | left: 0,
49 | right: 0,
50 | child: Container(
51 | height: 100,
52 | decoration: BoxDecoration(
53 | color: Colors.white,
54 | borderRadius: BorderRadius.circular(15),
55 | boxShadow: [
56 | BoxShadow(
57 | blurRadius: 10,
58 | color: Color.fromRGBO(78, 80, 88, 0.05),
59 | spreadRadius: 0.1)
60 | ]),
61 | child: Padding(
62 | padding: const EdgeInsets.all(10.0),
63 | child: Row(
64 | crossAxisAlignment: CrossAxisAlignment.center,
65 | children: [
66 | if (me != null)
67 | Text(me.oweMeAmount.toString(),
68 | style: Theme.of(context)
69 | .textTheme
70 | .headline3
71 | .copyWith(color: Theme.of(context).accentColor))
72 | else
73 | Expanded(child: Center(child: YOMSpinner()))
74 | ],
75 | )),
76 | ),
77 | ),
78 | ],
79 | ),
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/HomePage/iOweSection.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // 📦 Package imports:
6 | import 'package:flutter_hooks/flutter_hooks.dart';
7 | import 'package:hooks_riverpod/hooks_riverpod.dart';
8 |
9 | // 🌎 Project imports:
10 | import 'package:YouOweMe/resources/providers.dart';
11 | import 'package:YouOweMe/resources/graphql/seva.dart';
12 | import 'package:YouOweMe/ui/Abstractions/yomSpinner.dart';
13 |
14 | class IOweSection extends HookWidget {
15 | @override
16 | Widget build(BuildContext context) {
17 | Seva$Query$User me = useProvider(meNotifierProvider).me;
18 |
19 | void goToIOwePage() {
20 | Navigator.of(context).pushNamed('i_owe_page');
21 | }
22 |
23 | return Container(
24 | height: 130,
25 | color: Colors.transparent,
26 | child: Stack(
27 | alignment: Alignment.bottomCenter,
28 | children: [
29 | Positioned(
30 | left: 0,
31 | top: 0,
32 | child: CupertinoButton(
33 | onPressed: goToIOwePage,
34 | minSize: 0,
35 | padding: EdgeInsets.all(0),
36 | child: Row(
37 | children: [
38 | Text("I Owe", style: Theme.of(context).textTheme.headline5),
39 | Icon(
40 | CupertinoIcons.right_chevron,
41 | color: Color.fromRGBO(78, 80, 88, 1),
42 | )
43 | ],
44 | ),
45 | )),
46 | Positioned(
47 | left: 0,
48 | right: 0,
49 | child: Container(
50 | height: 100,
51 | decoration: BoxDecoration(
52 | color: Colors.white,
53 | borderRadius: BorderRadius.circular(15),
54 | boxShadow: [
55 | BoxShadow(
56 | blurRadius: 10,
57 | color: Color.fromRGBO(78, 80, 88, 0.05),
58 | spreadRadius: 0.1)
59 | ]),
60 | child: Padding(
61 | padding: const EdgeInsets.all(10.0),
62 | child: Row(
63 | crossAxisAlignment: CrossAxisAlignment.center,
64 | children: [
65 | if (me != null)
66 | Text(me.iOweAmount.toString(),
67 | style: Theme.of(context)
68 | .textTheme
69 | .headline3
70 | .copyWith(color: Theme.of(context).accentColor))
71 | else
72 | Expanded(
73 | child: Center(
74 | child: YOMSpinner(),
75 | ))
76 | ],
77 | )),
78 | ),
79 | )
80 | ],
81 | ),
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/mobileApp/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
9 |
10 |
11 |
15 |
22 |
26 |
30 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
50 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IntroFlow/permissionsFlow/contactPermissions.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // 📦 Package imports:
6 | import 'package:flutter_hooks/flutter_hooks.dart';
7 | import 'package:hooks_riverpod/hooks_riverpod.dart';
8 | import 'package:permission_handler/permission_handler.dart';
9 |
10 | // 🌎 Project imports:
11 | import 'package:YouOweMe/ui/IntroFlow/providers.dart';
12 |
13 | class ContactsPermissions extends HookWidget {
14 | @override
15 | Widget build(BuildContext context) {
16 | final PageController pageController =
17 | useProvider(introFlowPageControllerProvider);
18 | final _size = MediaQuery.of(context).size;
19 |
20 | void nextPage() {
21 | pageController.nextPage(
22 | duration: Duration(milliseconds: 200), curve: Curves.easeInOutQuad);
23 | }
24 |
25 | SizedBox _spacer(int padding, [int minus = 0]) {
26 | return SizedBox(height: (_size.height / padding) - minus);
27 | }
28 |
29 | void allowContact() async {
30 | PermissionStatus status = await Permission.contacts.request();
31 | print(status);
32 | if (status.isGranted) {
33 | nextPage();
34 | }
35 | }
36 |
37 | return Padding(
38 | padding: EdgeInsets.all(15),
39 | child: Stack(
40 | alignment: Alignment.center,
41 | fit: StackFit.expand,
42 | children: [
43 | Positioned.fill(
44 | bottom: 65,
45 | child: SingleChildScrollView(
46 | child: Column(
47 | crossAxisAlignment: CrossAxisAlignment.start,
48 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
49 | children: [
50 | _spacer(18, 20),
51 | Text("We need to read your Contacts.",
52 | style: Theme.of(context)
53 | .textTheme
54 | .headline1
55 | .copyWith(fontSize: _size.width / 8)),
56 | _spacer(16),
57 | Center(
58 | child: Container(
59 | height: 60,
60 | width: 400,
61 | child: CupertinoButton(
62 | color: CupertinoColors.activeGreen,
63 | child: Text('Allow Contacts'),
64 | onPressed: allowContact),
65 | ),
66 | ),
67 | _spacer(12),
68 | Image.asset("assets/scribbles/karlsson_contact_page.png")
69 | ],
70 | ),
71 | ),
72 | ),
73 | Positioned(
74 | bottom: 0,
75 | child: Container(
76 | height: 60,
77 | width: 400,
78 | child: CupertinoButton(
79 | key: Key("contact_permission_next"),
80 | color: Theme.of(context).accentColor,
81 | child: Text('Next'),
82 | onPressed: nextPage),
83 | ))
84 | ],
85 | ),
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IntroFlow/permissionsFlow/notificationsPermission.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // 📦 Package imports:
6 | import 'package:flutter_hooks/flutter_hooks.dart';
7 | import 'package:hooks_riverpod/hooks_riverpod.dart';
8 | import 'package:permission_handler/permission_handler.dart';
9 |
10 | // 🌎 Project imports:
11 | import 'package:YouOweMe/ui/IntroFlow/providers.dart';
12 |
13 | class NotificationsPermissions extends HookWidget {
14 | @override
15 | Widget build(BuildContext context) {
16 | final PageController pageController =
17 | useProvider(introFlowPageControllerProvider);
18 | final _size = MediaQuery.of(context).size;
19 | void nextPage() {
20 | pageController.nextPage(
21 | duration: Duration(milliseconds: 200), curve: Curves.easeInOutQuad);
22 | }
23 |
24 | SizedBox _spacer(int padding, [int minus = 0]) {
25 | return SizedBox(height: (_size.height / padding) - minus);
26 | }
27 |
28 | void allowNotifications() async {
29 | PermissionStatus status = await Permission.notification.request();
30 | print(status);
31 | if (status.isGranted) {
32 | nextPage();
33 | }
34 | }
35 |
36 | return Padding(
37 | padding: EdgeInsets.all(15),
38 | child: Stack(
39 | alignment: Alignment.center,
40 | fit: StackFit.expand,
41 | children: [
42 | Positioned.fill(
43 | bottom: 65,
44 | child: SingleChildScrollView(
45 | child: Column(
46 | crossAxisAlignment: CrossAxisAlignment.start,
47 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
48 | children: [
49 | _spacer(18, 20),
50 | Text("We need Notification Permissions.",
51 | style: Theme.of(context)
52 | .textTheme
53 | .headline1
54 | .copyWith(fontSize: _size.width / 8)),
55 | _spacer(16),
56 | Center(
57 | child: Container(
58 | height: 60,
59 | width: 400,
60 | child: CupertinoButton(
61 | color: CupertinoColors.activeGreen,
62 | child: Text('Allow Notifications'),
63 | onPressed: allowNotifications),
64 | ),
65 | ),
66 | Image.asset("assets/scribbles/karlsson_paper_plane.png")
67 | ],
68 | ),
69 | ),
70 | ),
71 | Positioned(
72 | bottom: 0,
73 | child: Container(
74 | height: 60,
75 | width: 400,
76 | child: CupertinoButton(
77 | key: Key("notification_permission_next"),
78 | color: Theme.of(context).accentColor,
79 | child: Text('Next'),
80 | onPressed: allowNotifications),
81 | ))
82 | ],
83 | ),
84 | );
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/mobileApp/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 |
--------------------------------------------------------------------------------
/mobileApp/lib/ui/IOwe/iOwePageBottomSheet.dart:
--------------------------------------------------------------------------------
1 | // 🐦 Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // 🌎 Project imports:
6 | import 'package:YouOweMe/ui/Abstractions/yomBottomSheet.dart';
7 | import 'package:YouOweMe/resources/graphql/seva.dart';
8 | import 'package:YouOweMe/ui/Abstractions/yomSpinner.dart';
9 | import 'package:YouOweMe/resources/extensions.dart';
10 |
11 | class IOwePageBottomSheet extends StatelessWidget {
12 | final Seva$Query$User$Owe owe;
13 | final ScrollController scrollController;
14 | IOwePageBottomSheet({@required this.scrollController, @required this.owe});
15 | @override
16 | Widget build(BuildContext context) {
17 | return ClipRRect(
18 | borderRadius: BorderRadius.only(
19 | topLeft: Radius.circular(15), topRight: Radius.circular(15)),
20 | child: Material(
21 | child: Padding(
22 | padding: EdgeInsets.all(15).copyWith(bottom: 10),
23 | child: Column(
24 | mainAxisSize: MainAxisSize.min,
25 | crossAxisAlignment: CrossAxisAlignment.start,
26 | children: [
27 | Text("Title", style: Theme.of(context).textTheme.headline5),
28 | Text(owe.title, style: Theme.of(context).textTheme.bodyText2),
29 | SizedBox(
30 | height: 20,
31 | ),
32 | if ([OweState.ACKNOWLEDGED, OweState.CREATED].contains(owe.state))
33 | Text("Amount To Be Paid",
34 | style: Theme.of(context).textTheme.headline5)
35 | else if (owe.state == OweState.PAID)
36 | Text("Amount Paid",
37 | style: Theme.of(context).textTheme.headline5),
38 | RichText(
39 | text: TextSpan(
40 | style: Theme.of(context).textTheme.headline1,
41 | children: [
42 | TextSpan(
43 | text: "₹",
44 | style:
45 | TextStyle(color: Theme.of(context).accentColor)),
46 | TextSpan(text: owe.amount.toInt().toString())
47 | ]),
48 | ),
49 | Text("Wait When was this Again?",
50 | style: Theme.of(context).textTheme.headline5),
51 | Text(owe.created.simpler,
52 | style: Theme.of(context).textTheme.bodyText2),
53 | if ([OweState.ACKNOWLEDGED, OweState.CREATED]
54 | .contains(owe.state)) ...[
55 | SizedBox(
56 | height: 20,
57 | ),
58 | Container(
59 | height: 60,
60 | width: 400,
61 | child: CupertinoButton(
62 | color: Theme.of(context).accentColor,
63 | child: Text('Pay Up!'),
64 | onPressed: () {
65 | showYomBottomSheet(
66 | context: context,
67 | builder: (a, b) => Center(child: YOMSpinner()));
68 | }),
69 | ),
70 | ]
71 | ],
72 | ),
73 | ),
74 | ),
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/mobileApp/windows/Runner.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 | {2761a4b5-57b2-4d50-a677-d20ddc17a7f1}
18 |
19 |
20 |
21 |
22 | Source Files
23 |
24 |
25 | Source Files
26 |
27 |
28 | Source Files
29 |
30 |
31 | Source Files
32 |
33 |
34 | Source Files
35 |
36 |
37 | Source Files
38 |
39 |
40 | Source Files\Client Wrapper
41 |
42 |
43 | Source Files\Client Wrapper
44 |
45 |
46 | Source Files\Client Wrapper
47 |
48 |
49 |
50 |
51 | Header Files
52 |
53 |
54 | Header Files
55 |
56 |
57 | Header Files
58 |
59 |
60 | Header Files
61 |
62 |
63 | Header Files
64 |
65 |
66 | Header Files
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Resource Files
75 |
76 |
77 |
78 |
79 | Resource Files
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/server/src/modules/Owe/NewOweResolver.ts:
--------------------------------------------------------------------------------
1 | import { Resolver, Mutation, Authorized, Ctx, Args, Arg, FieldResolver, Root } from "type-graphql";
2 | import { Owe, OweState } from "../../models/Owe";
3 | import { ApplicationContext } from "../../utils/appContext";
4 | import { firestore, auth } from "../../db/firebase";
5 | import { NewOweInputType } from "./newOwe/newOweInputType";
6 | import { Timestamp, DocumentReference } from "@google-cloud/firestore"
7 | import { User } from "../../models/User";
8 |
9 | import { UserResolver } from "../User/UserResolver"
10 | import { OweResolver } from "./OweResolver";
11 |
12 |
13 |
14 | @Resolver(Owe)
15 | export class NewOweResolver {
16 |
17 | @Authorized()
18 | @Mutation(() => Owe)
19 | async newOwe(
20 | @Arg("data")
21 | {
22 | title,
23 | amount,
24 | issuedToID,
25 | mobileNo,
26 | displayName
27 | }: NewOweInputType,
28 | @Ctx() context: ApplicationContext) {
29 | console.log(mobileNo)
30 | const userId = context.req.headers.authorization!
31 | const userRef = firestore.collection('users').doc(userId)
32 | const userSnapshot = await userRef.get()
33 | //Throw error when IssuedToID === context user ID
34 | if (issuedToID && issuedToID == userId) {
35 | throw new Error("Can't Lend yourself money")
36 | }
37 | if (!userSnapshot.exists) {
38 | throw Error("User Not Present")
39 | }
40 | let issuedToRef;
41 | if (issuedToID) {
42 | issuedToRef = firestore.collection('users').doc(issuedToID)
43 | } else if (mobileNo) {
44 | issuedToRef = await getUserRefFromMobileNo(mobileNo, displayName)
45 | } else {
46 | throw new Error("Either mobileNo or issuedToID is required")
47 | }
48 | const owe = {
49 | title: title,
50 | amount: amount,
51 | state: OweState.CREATED,
52 | issuedToRef: issuedToRef,
53 | created: Timestamp.fromMillis(Date.now())
54 | }
55 | const oweRef = await userRef.collection('owes').add(owe)
56 | const oweResponse = await new OweResolver().getOweFromRef(oweRef)
57 | return oweResponse
58 | }
59 | }
60 |
61 | /*
62 | The Logic for this function is as:
63 | 1. Query for the user in Firestore.
64 | 2. If user exists return his ref.
65 | 3. If the User does not exist, create an Anonymuous User in Firebase Auth
66 | and return that ref. ==> As a side effect of this, this Anonymus User's Document should be
67 | created with name and mobile_no
68 | //TODO think this out.
69 | */
70 |
71 | const getUserRefFromMobileNo = async (mobileNo: string, displayName: string): Promise => {
72 | //TODO change this to a auth query in the future.
73 | const query = firestore.collection('users').where("mobile_no", "==", mobileNo);
74 | const users = await query.get()
75 | if (users.empty) {
76 | if (displayName) {
77 | const user = await auth.createUser({
78 | phoneNumber: mobileNo,
79 | displayName: displayName
80 | })
81 | const userRef = firestore.collection('users').doc(user.uid)
82 | return userRef
83 | }
84 | throw new Error(`Can't Find user with mobile_no ${mobileNo}`)
85 | }
86 | const userRef = users.docs[0].ref
87 | return userRef
88 | }
--------------------------------------------------------------------------------
/mobileApp/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def parse_KV_file(file, separator='=')
14 | file_abs_path = File.expand_path(file)
15 | if !File.exists? file_abs_path
16 | return [];
17 | end
18 | generated_key_values = {}
19 | skip_line_start_symbols = ["#", "/"]
20 | File.foreach(file_abs_path) do |line|
21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
22 | plugin = line.split(pattern=separator)
23 | if plugin.length == 2
24 | podname = plugin[0].strip()
25 | path = plugin[1].strip()
26 | podpath = File.expand_path("#{path}", file_abs_path)
27 | generated_key_values[podname] = podpath
28 | else
29 | puts "Invalid plugin specification: #{line}"
30 | end
31 | end
32 | generated_key_values
33 | end
34 |
35 | target 'Runner' do
36 | use_frameworks!
37 | use_modular_headers!
38 |
39 | # Flutter Pod
40 |
41 | copied_flutter_dir = File.join(__dir__, 'Flutter')
42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
48 |
49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
50 | unless File.exist?(generated_xcode_build_settings_path)
51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
52 | end
53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
55 |
56 | unless File.exist?(copied_framework_path)
57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
58 | end
59 | unless File.exist?(copied_podspec_path)
60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
61 | end
62 | end
63 |
64 | # Keep pod path relative so it can be checked into Podfile.lock.
65 | pod 'Flutter', :path => 'Flutter'
66 |
67 | # Plugin Pods
68 |
69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
70 | # referring to absolute paths on developers' machines.
71 | system('rm -rf .symlinks')
72 | system('mkdir -p .symlinks/plugins')
73 | plugin_pods = parse_KV_file('../.flutter-plugins')
74 | plugin_pods.each do |name, path|
75 | symlink = File.join('.symlinks', 'plugins', name)
76 | File.symlink(path, symlink)
77 | pod name, :path => File.join(symlink, 'ios')
78 | end
79 | end
80 |
81 | post_install do |installer|
82 | installer.pods_project.targets.each do |target|
83 | target.build_configurations.each do |config|
84 | config.build_settings['ENABLE_BITCODE'] = 'NO'
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/mobileApp/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef WIN32_WINDOW_H_
2 | #define WIN32_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 | #include
9 | #include
10 |
11 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
12 | // inherited from by classes that wish to specialize with custom
13 | // rendering and input handling
14 | class Win32Window {
15 | public:
16 | struct Point {
17 | unsigned int x;
18 | unsigned int y;
19 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
20 | };
21 |
22 | struct Size {
23 | unsigned int width;
24 | unsigned int height;
25 | Size(unsigned int width, unsigned int height)
26 | : width(width), height(height) {}
27 | };
28 |
29 | Win32Window();
30 | virtual ~Win32Window();
31 |
32 | // Creates and shows a win32 window with |title| and position and size using
33 | // |origin| and |size|. New windows are created on the default monitor. Window
34 | // sizes are specified to the OS in physical pixels, hence to ensure a
35 | // consistent size to will treat the width height passed in to this function
36 | // as logical pixels and scale to appropriate for the default monitor. Returns
37 | // true if the window was created successfully.
38 | bool CreateAndShow(const std::wstring& title,
39 | const Point& origin,
40 | const Size& size);
41 |
42 | // Release OS resources associated with window.
43 | void Destroy();
44 |
45 | // Inserts |content| into the window tree.
46 | void SetChildContent(HWND content);
47 |
48 | // Returns the backing Window handle to enable clients to set icon and other
49 | // window properties. Returns nullptr if the window has been destroyed.
50 | HWND GetHandle();
51 |
52 | // If true, closing this window will quit the application.
53 | void SetQuitOnClose(bool quit_on_close);
54 |
55 | protected:
56 | // Processes and route salient window messages for mouse handling,
57 | // size change and DPI. Delegates handling of these to member overloads that
58 | // inheriting classes can handle.
59 | virtual LRESULT MessageHandler(HWND window,
60 | UINT const message,
61 | WPARAM const wparam,
62 | LPARAM const lparam) noexcept;
63 |
64 | // Called when CreateAndShow is called, allowing subclass window-related
65 | // setup.
66 | virtual void OnCreate();
67 |
68 | // Called when Destroy is called.
69 | virtual void OnDestroy();
70 |
71 | private:
72 | friend class WindowClassRegistrar;
73 |
74 | // OS callback called by message pump. Handles the WM_NCCREATE message which
75 | // is passed when the non-client area is being created and enables automatic
76 | // non-client DPI scaling so that the non-client area automatically
77 | // responsponds to changes in DPI. All other messages are handled by
78 | // MessageHandler.
79 | static LRESULT CALLBACK WndProc(HWND const window,
80 | UINT const message,
81 | WPARAM const wparam,
82 | LPARAM const lparam) noexcept;
83 |
84 | // Retrieves a class instance pointer for |window|
85 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
86 |
87 | bool quit_on_close_ = false;
88 |
89 | // window handle for top level window.
90 | HWND window_handle_ = nullptr;
91 |
92 | // window handle for hosted content.
93 | HWND child_content_ = nullptr;
94 | };
95 |
96 | #endif // WIN32_WINDOW_H_
97 |
--------------------------------------------------------------------------------