├── .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 | ![Mobile App Build](https://github.com/preetjdp/YouOweMe/workflows/Mobile%20App%20Build/badge.svg) 12 | 13 | ![Server Build](https://github.com/preetjdp/YouOweMe/workflows/Server%20Build/badge.svg) 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 | ![](https://media.giphy.com/media/l3mZasrfwrWUMnndS/giphy.gif) 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 | ![screenshot](https://user-images.githubusercontent.com/27439197/76833208-efea3f00-6850-11ea-867a-231a47072f50.png) 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 | --------------------------------------------------------------------------------