├── .gitignore ├── .metadata ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── google-services.json │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── code │ │ │ │ └── auth │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── icon.png └── tones │ └── message_tone.mp3 ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── GoogleService-Info.plist │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ ├── Runner.entitlements │ └── RunnerProfile.entitlements └── build │ ├── Pods.build │ └── Release-iphonesimulator │ │ ├── AppAuth.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FBAEMKit.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FBSDKCoreKit-FacebookSDKStrings.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FBSDKCoreKit.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FBSDKCoreKit_Basics.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FBSDKLoginKit.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── Firebase.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FirebaseAnalytics.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FirebaseCore.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FirebaseCoreDiagnostics.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── FirebaseInstallations.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── Flutter.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── GTMAppAuth.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── GTMSessionFetcher.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── GoogleAppMeasurement.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── GoogleDataTransport.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── GoogleSignIn.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── GoogleUtilities.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── Pods-Runner.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── PromisesObjC.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── flutter_facebook_auth.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── flutter_secure_storage.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── google_sign_in.build │ │ ├── dgph │ │ └── dgph~ │ │ ├── nanopb.build │ │ ├── dgph │ │ └── dgph~ │ │ └── sign_in_with_apple.build │ │ ├── dgph │ │ └── dgph~ │ └── XCBuildData │ ├── 581cef65247af86137256da0cb1a8e67-buildRequest.json │ ├── 581cef65247af86137256da0cb1a8e67-desc.xcbuild │ ├── 581cef65247af86137256da0cb1a8e67-manifest.xcbuild │ ├── 581cef65247af86137256da0cb1a8e67-targetGraph.txt │ ├── BuildDescriptionCacheIndex-2732f861f69bdb8a3f78de4705c510f8 │ ├── ba80ae0bba6832224845d3bb2897fc2c-buildRequest.json │ ├── ba80ae0bba6832224845d3bb2897fc2c-targetGraph.txt │ ├── build.db │ ├── e4be1c1cfe6a5f866c7dd2496bc80c69-buildRequest.json │ ├── e4be1c1cfe6a5f866c7dd2496bc80c69-desc.xcbuild │ ├── e4be1c1cfe6a5f866c7dd2496bc80c69-manifest.xcbuild │ ├── e4be1c1cfe6a5f866c7dd2496bc80c69-targetGraph.txt │ ├── f005355584a956fbe241e8ef6200384c-buildRequest.json │ ├── f005355584a956fbe241e8ef6200384c-desc.xcbuild │ ├── f005355584a956fbe241e8ef6200384c-manifest.xcbuild │ ├── f005355584a956fbe241e8ef6200384c-targetGraph.txt │ ├── fda55b95a3754a706935e2e7306bae3e-buildRequest.json │ ├── fda55b95a3754a706935e2e7306bae3e-desc.xcbuild │ ├── fda55b95a3754a706935e2e7306bae3e-manifest.xcbuild │ └── fda55b95a3754a706935e2e7306bae3e-targetGraph.txt ├── lib ├── main.dart └── src │ ├── app.dart │ ├── app_router.dart │ ├── constants │ └── environments.dart │ ├── core │ └── socket.dart │ ├── features │ ├── auth │ │ ├── logic │ │ │ ├── cubit │ │ │ │ └── auth_cubit.dart │ │ │ ├── interceptors │ │ │ │ └── auth_token_interceptor.dart │ │ │ ├── models │ │ │ │ ├── tokens.dart │ │ │ │ └── user.dart │ │ │ ├── provider │ │ │ │ └── auth_api_provider.dart │ │ │ └── repository │ │ │ │ └── auth_repository.dart │ │ └── views │ │ │ └── screens │ │ │ ├── login_screen.dart │ │ │ ├── recover_screen.dart │ │ │ └── register_screen.dart │ ├── home │ │ └── views │ │ │ ├── screens │ │ │ └── home_screen.dart │ │ │ └── widgets │ │ │ ├── authenticated_home.dart │ │ │ └── non_authenticated_home.dart │ ├── messages │ │ ├── logic │ │ │ ├── bloc │ │ │ │ ├── direct_message_bloc.dart │ │ │ │ ├── direct_message_event.dart │ │ │ │ ├── direct_message_state.dart │ │ │ │ ├── message_bloc.dart │ │ │ │ ├── message_event.dart │ │ │ │ └── message_state.dart │ │ │ ├── enum │ │ │ │ └── message_type.dart │ │ │ ├── models │ │ │ │ ├── message.dart │ │ │ │ └── typing.dart │ │ │ ├── providers │ │ │ │ └── message_api_provider.dart │ │ │ └── repository │ │ │ │ └── message_repository.dart │ │ └── views │ │ │ ├── screens │ │ │ └── direct_message_screen.dart │ │ │ └── widgets │ │ │ └── messages.dart │ ├── notification │ │ ├── logic │ │ │ ├── enums │ │ │ │ └── notification_type.dart │ │ │ ├── provider │ │ │ │ └── subscription_api_provider.dart │ │ │ └── repository │ │ │ │ ├── notification_repository.dart │ │ │ │ └── subscription_repository.dart │ │ └── views │ │ │ └── widgets │ │ │ └── notification_handler.dart │ ├── room │ │ ├── logic │ │ │ ├── bloc │ │ │ │ ├── room_bloc.dart │ │ │ │ ├── room_event.dart │ │ │ │ ├── room_state.dart │ │ │ │ ├── rooms_bloc.dart │ │ │ │ ├── rooms_event.dart │ │ │ │ └── rooms_state.dart │ │ │ ├── models │ │ │ │ └── room.dart │ │ │ ├── provider │ │ │ │ └── room_api_provider.dart │ │ │ └── repository │ │ │ │ └── room_repository.dart │ │ └── views │ │ │ ├── screens │ │ │ ├── room_screen.dart │ │ │ └── rooms_screen.dart │ │ │ └── widgets │ │ │ ├── dialog │ │ │ ├── join_room_dialog.dart │ │ │ └── upsert_room_dialog.dart │ │ │ └── room_tile.dart │ ├── settings │ │ ├── logic │ │ │ ├── provider │ │ │ │ └── settings_api_provider.dart │ │ │ └── settings_repository.dart │ │ └── views │ │ │ └── screens │ │ │ └── settings_screen.dart │ └── user │ │ └── logic │ │ ├── provider │ │ └── user_api_provider.dart │ │ └── repository │ │ └── user_repository.dart │ └── shared │ ├── logic │ └── http │ │ ├── api.dart │ │ └── interceptors │ │ └── error_dialog_interceptor.dart │ └── views │ └── widgets │ ├── circles_background.dart │ ├── dialog │ ├── alert_dialog_widget.dart │ └── confirm_dialog_widget.dart │ ├── go_back_button.dart │ ├── main_text_field.dart │ ├── next_button.dart │ ├── scroll_close_keyboard.dart │ ├── scrollable_form.dart │ ├── typing_indicator.dart │ ├── underlined_button.dart │ └── user_status.dart ├── pubspec.lock ├── pubspec.yaml └── web ├── favicon.png ├── icons ├── Icon-192.png └── Icon-512.png ├── index.html └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | .vscode -------------------------------------------------------------------------------- /.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: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Flutter Auth App (Messages, Rooms, Login, Register, Google Login, Facebook Login, Apple Login) 2 | 3 | To use this client, get the [server](https://github.com/DenzelCode/nest-auth) up and running. 4 | 5 | Try it out now! 6 | 7 | App Store: https://apps.apple.com/us/app/codeauth/id1575457893 8 | 9 | Google Play: https://play.google.com/store/apps/details?id=com.code.auth (Outdated) 10 | 11 | Images: 12 |

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |

22 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'com.google.gms.google-services' 26 | 27 | apply plugin: 'kotlin-android' 28 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 29 | 30 | def keystoreProperties = new Properties() 31 | def keystorePropertiesFile = rootProject.file('key.properties') 32 | if (keystorePropertiesFile.exists()) { 33 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 34 | } 35 | 36 | android { 37 | compileSdkVersion 30 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.code.auth" 46 | minSdkVersion 18 47 | targetSdkVersion 30 48 | multiDexEnabled true 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | signingConfigs { 54 | release { 55 | keyAlias keystoreProperties['keyAlias'] 56 | keyPassword keystoreProperties['keyPassword'] 57 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 58 | storePassword keystoreProperties['storePassword'] 59 | } 60 | } 61 | buildTypes { 62 | release { 63 | signingConfig signingConfigs.release 64 | } 65 | } 66 | } 67 | 68 | flutter { 69 | source '../..' 70 | } 71 | 72 | dependencies { 73 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 74 | 75 | implementation platform('com.google.firebase:firebase-bom:28.2.1') 76 | 77 | implementation 'com.google.firebase:firebase-analytics-ktx' 78 | 79 | implementation 'androidx.multidex:multidex:2.0.1' 80 | 81 | implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' 82 | } 83 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "331672215174", 4 | "project_id": "codeauth-320604", 5 | "storage_bucket": "codeauth-320604.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:331672215174:android:8f306094623f21c4fabb07", 11 | "android_client_info": { 12 | "package_name": "com.code.auth" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "331672215174-qpt9riksekmpc4a4gd2tg9t4ja1nv6aq.apps.googleusercontent.com", 18 | "client_type": 1, 19 | "android_info": { 20 | "package_name": "com.code.auth", 21 | "certificate_hash": "d7a7e1b53089f15ed60d80c69492f5ef1f11ce32" 22 | } 23 | }, 24 | { 25 | "client_id": "331672215174-0hlpm8fhjphiou05ovsd82vglor401ct.apps.googleusercontent.com", 26 | "client_type": 3 27 | } 28 | ], 29 | "api_key": [ 30 | { 31 | "current_key": "AIzaSyCBUUMHX_e-NZU50Mtmqd0hE_Yzijk8-v4" 32 | } 33 | ], 34 | "services": { 35 | "appinvite_service": { 36 | "other_platform_oauth_client": [ 37 | { 38 | "client_id": "331672215174-0hlpm8fhjphiou05ovsd82vglor401ct.apps.googleusercontent.com", 39 | "client_type": 3 40 | }, 41 | { 42 | "client_id": "331672215174-00qqqsq30acj5lgsikfghp6luk0k34j0.apps.googleusercontent.com", 43 | "client_type": 2, 44 | "ios_info": { 45 | "bundle_id": "com.code.auth" 46 | } 47 | } 48 | ] 49 | } 50 | } 51 | } 52 | ], 53 | "configuration_version": "1" 54 | } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 47 | 51 | 55 | 60 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/code/auth/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.code.auth 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/android/app/src/main/res/mipmap/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeAuth 4 | 535472397651204 5 | fb535472397651204 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.5.10' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.8' 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 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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-6.7-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/assets/icon.png -------------------------------------------------------------------------------- /assets/tones/message_tone.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/assets/tones/message_tone.mp3 -------------------------------------------------------------------------------- /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/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '10.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-App-20x20@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "Icon-App-20x20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "Icon-App-29x29@1x.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "Icon-App-29x29@2x.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "Icon-App-29x29@3x.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "Icon-App-40x40@2x.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "Icon-App-40x40@3x.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "Icon-App-60x60@2x.png", 47 | "idiom" : "iphone", 48 | "scale" : "2x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "Icon-App-60x60@3x.png", 53 | "idiom" : "iphone", 54 | "scale" : "3x", 55 | "size" : "60x60" 56 | }, 57 | { 58 | "filename" : "Icon-App-20x20@1x.png", 59 | "idiom" : "ipad", 60 | "scale" : "1x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "Icon-App-20x20@2x.png", 65 | "idiom" : "ipad", 66 | "scale" : "2x", 67 | "size" : "20x20" 68 | }, 69 | { 70 | "filename" : "Icon-App-29x29@1x.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "Icon-App-29x29@2x.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "29x29" 80 | }, 81 | { 82 | "filename" : "Icon-App-40x40@1x.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "Icon-App-40x40@2x.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "40x40" 92 | }, 93 | { 94 | "filename" : "Icon-App-76x76@1x.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "Icon-App-76x76@2x.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "76x76" 104 | }, 105 | { 106 | "filename" : "Icon-App-83.5x83.5@2x.png", 107 | "idiom" : "ipad", 108 | "scale" : "2x", 109 | "size" : "83.5x83.5" 110 | }, 111 | { 112 | "filename" : "Icon-App-1024x1024@1x.png", 113 | "idiom" : "ios-marketing", 114 | "scale" : "1x", 115 | "size" : "1024x1024" 116 | } 117 | ], 118 | "info" : { 119 | "author" : "xcode", 120 | "version" : 1 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 331672215174-00qqqsq30acj5lgsikfghp6luk0k34j0.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.331672215174-00qqqsq30acj5lgsikfghp6luk0k34j0 9 | ANDROID_CLIENT_ID 10 | 331672215174-qpt9riksekmpc4a4gd2tg9t4ja1nv6aq.apps.googleusercontent.com 11 | API_KEY 12 | AIzaSyDooN2gjVrf43-e3r5vhXBcaSLapOh5BkU 13 | GCM_SENDER_ID 14 | 331672215174 15 | PLIST_VERSION 16 | 1 17 | BUNDLE_ID 18 | com.code.auth 19 | PROJECT_ID 20 | codeauth-320604 21 | STORAGE_BUCKET 22 | codeauth-320604.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:331672215174:ios:b701c53bfcc01d47fabb07 35 | 36 | 37 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | CodeAuth 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | auth 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLSchemes 29 | 30 | fb535472397651204 31 | com.googleusercontent.apps.331672215174-00qqqsq30acj5lgsikfghp6luk0k34j0 32 | 33 | 34 | 35 | CFBundleVersion 36 | $(CURRENT_PROJECT_VERSION) 37 | FacebookAppID 38 | 535472397651204 39 | FacebookDisplayName 40 | CodeAuth 41 | LSApplicationQueriesSchemes 42 | 43 | fbapi 44 | fb-messenger-share-api 45 | fbauth2 46 | fbshareextension 47 | 48 | LSRequiresIPhoneOS 49 | 50 | UIApplicationSceneManifest 51 | 52 | UIApplicationSupportsMultipleScenes 53 | 54 | 55 | UILaunchStoryboardName 56 | LaunchScreen 57 | UIMainStoryboardFile 58 | Main 59 | UISupportedInterfaceOrientations 60 | 61 | UIInterfaceOrientationPortrait 62 | 63 | UISupportedInterfaceOrientations~ipad 64 | 65 | UIInterfaceOrientationPortrait 66 | UIInterfaceOrientationPortraitUpsideDown 67 | UIInterfaceOrientationLandscapeLeft 68 | UIInterfaceOrientationLandscapeRight 69 | 70 | UIViewControllerBasedStatusBarAppearance 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.applesignin 8 | 9 | Default 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/RunnerProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.applesignin 8 | 9 | Default 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/AppAuth.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/AppAuth.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBAEMKit.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBAEMKit.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBSDKCoreKit-FacebookSDKStrings.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBSDKCoreKit-FacebookSDKStrings.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBSDKCoreKit.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBSDKCoreKit.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBSDKCoreKit_Basics.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBSDKCoreKit_Basics.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBSDKLoginKit.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FBSDKLoginKit.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Firebase.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Firebase.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseAnalytics.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseAnalytics.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseCore.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseCore.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseCoreDiagnostics.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseCoreDiagnostics.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseInstallations.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/FirebaseInstallations.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Flutter.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Flutter.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GTMAppAuth.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GTMAppAuth.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GTMSessionFetcher.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GTMSessionFetcher.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleAppMeasurement.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleAppMeasurement.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleDataTransport.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleDataTransport.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleSignIn.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleSignIn.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleUtilities.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/GoogleUtilities.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/Pods-Runner.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/PromisesObjC.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/PromisesObjC.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/flutter_facebook_auth.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/flutter_facebook_auth.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/flutter_secure_storage.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/flutter_secure_storage.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/google_sign_in.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/google_sign_in.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/nanopb.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/nanopb.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/sign_in_with_apple.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Aug 25 202119:34:47 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/sign_in_with_apple.build/dgph~: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05 /Users 2 | denzelcode DocumentsCodeFlutterauthiosPods -------------------------------------------------------------------------------- /ios/build/XCBuildData/581cef65247af86137256da0cb1a8e67-buildRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand" : "prepareForIndexing", 3 | "configuredTargets" : [ 4 | { 5 | "guid" : "e25a7932c7d6b18d11734b7ff1ae15bf88a783a885d8b0b3beb2e9f90bde3f49" 6 | } 7 | ], 8 | "continueBuildingAfterErrors" : true, 9 | "enableIndexBuildArena" : false, 10 | "hideShellScriptEnvironment" : false, 11 | "parameters" : { 12 | "action" : "build", 13 | "activeArchitecture" : "x86_64", 14 | "activeRunDestination" : { 15 | "disableOnlyActiveArch" : false, 16 | "platform" : "iphonesimulator", 17 | "sdk" : "iphonesimulator14.5", 18 | "sdkVariant" : "iphonesimulator", 19 | "supportedArchitectures" : [ 20 | "x86_64" 21 | ], 22 | "targetArchitecture" : "x86_64" 23 | }, 24 | "arenaInfo" : { 25 | "buildIntermediatesPath" : "", 26 | "buildProductsPath" : "", 27 | "derivedDataPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData", 28 | "indexDataStoreFolderPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/DataStore", 29 | "indexEnableDataStore" : true, 30 | "indexPCHPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/PrecompiledHeaders", 31 | "pchPath" : "" 32 | }, 33 | "configurationName" : "Debug", 34 | "overrides" : { 35 | "synthesized" : { 36 | "table" : { 37 | "ASSETCATALOG_FILTER_FOR_DEVICE_MODEL" : "iPod9,1", 38 | "ASSETCATALOG_FILTER_FOR_DEVICE_OS_VERSION" : "14.5", 39 | "BUILD_ACTIVE_RESOURCES_ONLY" : "YES", 40 | "ENABLE_PREVIEWS" : "NO", 41 | "TARGET_DEVICE_IDENTIFIER" : "D5626EA5-63CA-4C42-810C-1FA3F088B2F3", 42 | "TARGET_DEVICE_MODEL" : "iPod9,1", 43 | "TARGET_DEVICE_OS_VERSION" : "14.5", 44 | "TARGET_DEVICE_PLATFORM_NAME" : "iphonesimulator" 45 | } 46 | } 47 | } 48 | }, 49 | "schemeCommand" : "launch", 50 | "shouldCollectMetrics" : false, 51 | "showNonLoggedProgress" : true, 52 | "useDryRun" : false, 53 | "useImplicitDependencies" : true, 54 | "useLegacyBuildLocations" : false, 55 | "useParallelTargets" : true 56 | } -------------------------------------------------------------------------------- /ios/build/XCBuildData/581cef65247af86137256da0cb1a8e67-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/build/XCBuildData/581cef65247af86137256da0cb1a8e67-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/581cef65247af86137256da0cb1a8e67-targetGraph.txt: -------------------------------------------------------------------------------- 1 | Target dependency graph (1 target) 2 | Runner in Runner -------------------------------------------------------------------------------- /ios/build/XCBuildData/BuildDescriptionCacheIndex-2732f861f69bdb8a3f78de4705c510f8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/build/XCBuildData/BuildDescriptionCacheIndex-2732f861f69bdb8a3f78de4705c510f8 -------------------------------------------------------------------------------- /ios/build/XCBuildData/ba80ae0bba6832224845d3bb2897fc2c-buildRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand" : "prepareForIndexing", 3 | "configuredTargets" : [ 4 | { 5 | "guid" : "e25a7932c7d6b18d11734b7ff1ae15bf88a783a885d8b0b3beb2e9f90bde3f49" 6 | } 7 | ], 8 | "continueBuildingAfterErrors" : true, 9 | "enableIndexBuildArena" : false, 10 | "hideShellScriptEnvironment" : false, 11 | "parameters" : { 12 | "action" : "build", 13 | "activeArchitecture" : "x86_64", 14 | "activeRunDestination" : { 15 | "disableOnlyActiveArch" : false, 16 | "platform" : "iphonesimulator", 17 | "sdk" : "iphonesimulator14.5", 18 | "sdkVariant" : "iphonesimulator", 19 | "supportedArchitectures" : [ 20 | "x86_64" 21 | ], 22 | "targetArchitecture" : "x86_64" 23 | }, 24 | "arenaInfo" : { 25 | "buildIntermediatesPath" : "", 26 | "buildProductsPath" : "", 27 | "derivedDataPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData", 28 | "indexDataStoreFolderPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/DataStore", 29 | "indexEnableDataStore" : true, 30 | "indexPCHPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/PrecompiledHeaders", 31 | "pchPath" : "" 32 | }, 33 | "configurationName" : "Debug", 34 | "overrides" : { 35 | "synthesized" : { 36 | "table" : { 37 | "ASSETCATALOG_FILTER_FOR_DEVICE_MODEL" : "iPod9,1", 38 | "ASSETCATALOG_FILTER_FOR_DEVICE_OS_VERSION" : "14.5", 39 | "BUILD_ACTIVE_RESOURCES_ONLY" : "YES", 40 | "ENABLE_PREVIEWS" : "NO", 41 | "TARGET_DEVICE_IDENTIFIER" : "D5626EA5-63CA-4C42-810C-1FA3F088B2F3", 42 | "TARGET_DEVICE_MODEL" : "iPod9,1", 43 | "TARGET_DEVICE_OS_VERSION" : "14.5", 44 | "TARGET_DEVICE_PLATFORM_NAME" : "iphonesimulator" 45 | } 46 | } 47 | } 48 | }, 49 | "schemeCommand" : "launch", 50 | "shouldCollectMetrics" : false, 51 | "showNonLoggedProgress" : true, 52 | "useDryRun" : false, 53 | "useImplicitDependencies" : true, 54 | "useLegacyBuildLocations" : false, 55 | "useParallelTargets" : true 56 | } -------------------------------------------------------------------------------- /ios/build/XCBuildData/ba80ae0bba6832224845d3bb2897fc2c-targetGraph.txt: -------------------------------------------------------------------------------- 1 | Target dependency graph (1 target) 2 | Runner in Runner -------------------------------------------------------------------------------- /ios/build/XCBuildData/build.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/build/XCBuildData/build.db -------------------------------------------------------------------------------- /ios/build/XCBuildData/e4be1c1cfe6a5f866c7dd2496bc80c69-buildRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand" : "prepareForIndexing", 3 | "configuredTargets" : [ 4 | { 5 | "guid" : "e25a7932c7d6b18d11734b7ff1ae15bf88a783a885d8b0b3beb2e9f90bde3f49" 6 | } 7 | ], 8 | "continueBuildingAfterErrors" : true, 9 | "enableIndexBuildArena" : false, 10 | "hideShellScriptEnvironment" : false, 11 | "parameters" : { 12 | "action" : "build", 13 | "activeArchitecture" : "x86_64", 14 | "activeRunDestination" : { 15 | "disableOnlyActiveArch" : false, 16 | "platform" : "iphonesimulator", 17 | "sdk" : "iphonesimulator14.5", 18 | "sdkVariant" : "iphonesimulator", 19 | "supportedArchitectures" : [ 20 | "x86_64" 21 | ], 22 | "targetArchitecture" : "x86_64" 23 | }, 24 | "arenaInfo" : { 25 | "buildIntermediatesPath" : "", 26 | "buildProductsPath" : "", 27 | "derivedDataPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData", 28 | "indexDataStoreFolderPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/DataStore", 29 | "indexEnableDataStore" : true, 30 | "indexPCHPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/PrecompiledHeaders", 31 | "pchPath" : "" 32 | }, 33 | "configurationName" : "Debug", 34 | "overrides" : { 35 | "synthesized" : { 36 | "table" : { 37 | "ASSETCATALOG_FILTER_FOR_DEVICE_MODEL" : "iPod9,1", 38 | "ASSETCATALOG_FILTER_FOR_DEVICE_OS_VERSION" : "14.5", 39 | "BUILD_ACTIVE_RESOURCES_ONLY" : "YES", 40 | "ENABLE_PREVIEWS" : "NO", 41 | "TARGET_DEVICE_IDENTIFIER" : "D5626EA5-63CA-4C42-810C-1FA3F088B2F3", 42 | "TARGET_DEVICE_MODEL" : "iPod9,1", 43 | "TARGET_DEVICE_OS_VERSION" : "14.5", 44 | "TARGET_DEVICE_PLATFORM_NAME" : "iphonesimulator" 45 | } 46 | } 47 | } 48 | }, 49 | "qos" : "default", 50 | "schemeCommand" : "launch", 51 | "shouldCollectMetrics" : false, 52 | "showNonLoggedProgress" : true, 53 | "useDryRun" : true, 54 | "useImplicitDependencies" : true, 55 | "useLegacyBuildLocations" : false, 56 | "useParallelTargets" : true 57 | } -------------------------------------------------------------------------------- /ios/build/XCBuildData/e4be1c1cfe6a5f866c7dd2496bc80c69-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/build/XCBuildData/e4be1c1cfe6a5f866c7dd2496bc80c69-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/e4be1c1cfe6a5f866c7dd2496bc80c69-targetGraph.txt: -------------------------------------------------------------------------------- 1 | Target dependency graph (1 target) 2 | Runner in Runner -------------------------------------------------------------------------------- /ios/build/XCBuildData/f005355584a956fbe241e8ef6200384c-buildRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand" : "prepareForIndexing", 3 | "configuredTargets" : [ 4 | { 5 | "guid" : "e25a7932c7d6b18d11734b7ff1ae15bf88a783a885d8b0b3beb2e9f90bde3f49" 6 | } 7 | ], 8 | "continueBuildingAfterErrors" : true, 9 | "enableIndexBuildArena" : false, 10 | "hideShellScriptEnvironment" : false, 11 | "parameters" : { 12 | "action" : "build", 13 | "activeArchitecture" : "arm64e", 14 | "activeRunDestination" : { 15 | "disableOnlyActiveArch" : true, 16 | "platform" : "iphoneos", 17 | "sdk" : "iphoneos14.5", 18 | "sdkVariant" : "iphoneos", 19 | "supportedArchitectures" : [ 20 | "armv4t", 21 | "armv5", 22 | "armv6", 23 | "armv7", 24 | "armv7f", 25 | "armv7s", 26 | "armv7k", 27 | "arm64", 28 | "arm64e" 29 | ], 30 | "targetArchitecture" : "arm64e" 31 | }, 32 | "arenaInfo" : { 33 | "buildIntermediatesPath" : "", 34 | "buildProductsPath" : "", 35 | "derivedDataPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData", 36 | "indexDataStoreFolderPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/DataStore", 37 | "indexEnableDataStore" : true, 38 | "indexPCHPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/PrecompiledHeaders", 39 | "pchPath" : "" 40 | }, 41 | "configurationName" : "Debug", 42 | "overrides" : { 43 | "synthesized" : { 44 | "table" : { 45 | "ENABLE_PREVIEWS" : "NO" 46 | } 47 | } 48 | } 49 | }, 50 | "qos" : "default", 51 | "schemeCommand" : "launch", 52 | "shouldCollectMetrics" : false, 53 | "showNonLoggedProgress" : true, 54 | "useDryRun" : true, 55 | "useImplicitDependencies" : true, 56 | "useLegacyBuildLocations" : false, 57 | "useParallelTargets" : true 58 | } -------------------------------------------------------------------------------- /ios/build/XCBuildData/f005355584a956fbe241e8ef6200384c-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/build/XCBuildData/f005355584a956fbe241e8ef6200384c-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/f005355584a956fbe241e8ef6200384c-targetGraph.txt: -------------------------------------------------------------------------------- 1 | Target dependency graph (1 target) 2 | Runner in Runner -------------------------------------------------------------------------------- /ios/build/XCBuildData/fda55b95a3754a706935e2e7306bae3e-buildRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand" : "prepareForIndexing", 3 | "configuredTargets" : [ 4 | { 5 | "guid" : "e25a7932c7d6b18d11734b7ff1ae15bf88a783a885d8b0b3beb2e9f90bde3f49" 6 | } 7 | ], 8 | "continueBuildingAfterErrors" : true, 9 | "enableIndexBuildArena" : false, 10 | "hideShellScriptEnvironment" : false, 11 | "parameters" : { 12 | "action" : "build", 13 | "activeArchitecture" : "x86_64", 14 | "activeRunDestination" : { 15 | "disableOnlyActiveArch" : false, 16 | "platform" : "iphonesimulator", 17 | "sdk" : "iphonesimulator14.5", 18 | "sdkVariant" : "iphonesimulator", 19 | "supportedArchitectures" : [ 20 | "x86_64" 21 | ], 22 | "targetArchitecture" : "x86_64" 23 | }, 24 | "arenaInfo" : { 25 | "buildIntermediatesPath" : "", 26 | "buildProductsPath" : "", 27 | "derivedDataPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData", 28 | "indexDataStoreFolderPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/DataStore", 29 | "indexEnableDataStore" : true, 30 | "indexPCHPath" : "/Users/denzelcode/Library/Developer/Xcode/DerivedData/Runner-goxwgqnqgwceananepkkdrscryax/Index/PrecompiledHeaders", 31 | "pchPath" : "" 32 | }, 33 | "configurationName" : "Debug", 34 | "overrides" : { 35 | "synthesized" : { 36 | "table" : { 37 | "ASSETCATALOG_FILTER_FOR_DEVICE_MODEL" : "iPod9,1", 38 | "ASSETCATALOG_FILTER_FOR_DEVICE_OS_VERSION" : "14.5", 39 | "BUILD_ACTIVE_RESOURCES_ONLY" : "YES", 40 | "ENABLE_PREVIEWS" : "NO", 41 | "TARGET_DEVICE_IDENTIFIER" : "D5626EA5-63CA-4C42-810C-1FA3F088B2F3", 42 | "TARGET_DEVICE_MODEL" : "iPod9,1", 43 | "TARGET_DEVICE_OS_VERSION" : "14.5", 44 | "TARGET_DEVICE_PLATFORM_NAME" : "iphonesimulator" 45 | } 46 | } 47 | } 48 | }, 49 | "qos" : "default", 50 | "schemeCommand" : "launch", 51 | "shouldCollectMetrics" : false, 52 | "showNonLoggedProgress" : true, 53 | "useDryRun" : true, 54 | "useImplicitDependencies" : true, 55 | "useLegacyBuildLocations" : false, 56 | "useParallelTargets" : true 57 | } -------------------------------------------------------------------------------- /ios/build/XCBuildData/fda55b95a3754a706935e2e7306bae3e-desc.xcbuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/ios/build/XCBuildData/fda55b95a3754a706935e2e7306bae3e-desc.xcbuild -------------------------------------------------------------------------------- /ios/build/XCBuildData/fda55b95a3754a706935e2e7306bae3e-targetGraph.txt: -------------------------------------------------------------------------------- 1 | Target dependency graph (1 target) 2 | Runner in Runner -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/app.dart'; 2 | import 'package:auth/src/features/notification/logic/repository/notification_repository.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | void main() async { 6 | WidgetsFlutterBinding.ensureInitialized(); 7 | 8 | await notificationRepository.setup(); 9 | 10 | runApp(MyApp()); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/app_router.dart'; 2 | import 'package:auth/src/features/auth/logic/cubit/auth_cubit.dart'; 3 | import 'package:auth/src/features/auth/logic/models/user.dart'; 4 | import 'package:auth/src/features/auth/logic/repository/auth_repository.dart'; 5 | import 'package:auth/src/features/home/views/screens/home_screen.dart'; 6 | import 'package:auth/src/features/notification/logic/repository/notification_repository.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_bloc/flutter_bloc.dart'; 9 | 10 | final GlobalKey applicationKey = GlobalKey(); 11 | final GlobalKey scaffoldMessengerKey = GlobalKey(); 12 | 13 | class MyApp extends StatefulWidget { 14 | MyApp({Key? key}) : super(key: key); 15 | 16 | @override 17 | _MyAppState createState() => _MyAppState(); 18 | } 19 | 20 | class _MyAppState extends State { 21 | final appRouter = AppRouter(); 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | 27 | notificationRepository.init(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return _InitProviders( 33 | child: MaterialApp( 34 | navigatorKey: applicationKey, 35 | scaffoldMessengerKey: scaffoldMessengerKey, 36 | debugShowCheckedModeBanner: false, 37 | initialRoute: HomeScreen.routeName, 38 | onGenerateRoute: appRouter.onGenerateRoute, 39 | theme: ThemeData.light().copyWith( 40 | colorScheme: ColorScheme.light().copyWith( 41 | primary: Color(0xff4C525C), 42 | secondary: Color(0xff4C525C), 43 | background: Color(0xff4C525C), 44 | ), 45 | appBarTheme: AppBarTheme(elevation: 0), 46 | primaryColor: Color(0xff4C525C), 47 | secondaryHeaderColor: Color(0xffFFAE48), 48 | highlightColor: Color(0xff58BFE6), 49 | indicatorColor: Color(0xff4C525C), 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | 56 | class _InitProviders extends StatelessWidget { 57 | final Widget child; 58 | 59 | const _InitProviders({Key? key, required this.child}) : super(key: key); 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return RepositoryProvider( 64 | create: (context) => AuthRepository(), 65 | child: BlocProvider( 66 | create: (context) => AuthCubit( 67 | authRepository: context.read(), 68 | ), 69 | child: BlocListener( 70 | listenWhen: (prev, curr) => prev != null && curr == null, 71 | listener: (context, user) { 72 | final route = ModalRoute.of(context)?.settings.name; 73 | 74 | if (user == null && route != HomeScreen.routeName) { 75 | Navigator.pushNamedAndRemoveUntil( 76 | applicationKey.currentContext as BuildContext, 77 | HomeScreen.routeName, 78 | (route) => false, 79 | ); 80 | } 81 | }, 82 | child: child, 83 | ), 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/app_router.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/views/screens/login_screen.dart'; 2 | import 'package:auth/src/features/auth/views/screens/recover_screen.dart'; 3 | import 'package:auth/src/features/auth/views/screens/register_screen.dart'; 4 | import 'package:auth/src/features/home/views/screens/home_screen.dart'; 5 | import 'package:auth/src/features/messages/views/screens/direct_message_screen.dart'; 6 | import 'package:auth/src/features/room/views/screens/room_screen.dart'; 7 | import 'package:auth/src/features/room/views/screens/rooms_screen.dart'; 8 | import 'package:auth/src/features/settings/views/screens/settings_screen.dart'; 9 | import 'package:flutter/material.dart'; 10 | 11 | class AppRouter { 12 | Route onGenerateRoute(RouteSettings settings) { 13 | switch (settings.name) { 14 | case HomeScreen.routeName: 15 | return HomeScreen.route(); 16 | case LoginScreen.routeName: 17 | return LoginScreen.route(); 18 | case RegisterScreen.routeName: 19 | return RegisterScreen.route(); 20 | case RecoverScreen.routeName: 21 | return RecoverScreen.route(); 22 | case RoomsScreen.routeName: 23 | return RoomsScreen.route(); 24 | case RoomScreen.routeName: 25 | return RoomScreen.route(settings); 26 | case DirectMessageScreen.routeName: 27 | return DirectMessageScreen.route(settings); 28 | case SettingsScreen.routeName: 29 | return SettingsScreen.route(); 30 | default: 31 | return HomeScreen.route(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/constants/environments.dart: -------------------------------------------------------------------------------- 1 | class _Environments { 2 | final web = 'https://nest-auth.ubbly.club'; 3 | final api = 'https://nest-auth.ubbly.club/api'; 4 | final socket = 'https://nest-auth.ubbly.club/'; 5 | 6 | final appleSignInClientId = 'nest-auth.ubbly.club'; 7 | final appleSignInRedirectUri = 8 | Uri.parse('https://nest-auth.ubbly.club/api/auth/apple-callback'); 9 | 10 | // final api = 'http://localhost:3000'; 11 | } 12 | 13 | _Environments environments = _Environments(); 14 | -------------------------------------------------------------------------------- /lib/src/core/socket.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:auth/src/app.dart'; 4 | import 'package:auth/src/constants/environments.dart'; 5 | import 'package:auth/src/features/auth/logic/repository/auth_repository.dart'; 6 | import 'package:auth/src/shared/views/widgets/dialog/alert_dialog_widget.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:socket_io_client/socket_io_client.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | 11 | class SocketManager { 12 | Socket socket = io( 13 | environments.socket, 14 | OptionBuilder() 15 | .setTransports(['websocket']) 16 | .disableAutoConnect() 17 | .setExtraHeaders({}) 18 | .build(), 19 | ); 20 | 21 | Future init([Function? onConnect]) async { 22 | if (socket.connected) { 23 | return socket; 24 | } 25 | 26 | final context = applicationKey.currentContext; 27 | 28 | final repository = context?.read(); 29 | 30 | if (repository == null) { 31 | return socket; 32 | } 33 | 34 | final accessToken = await repository.getAccessToken(); 35 | 36 | socket.io.options['extraHeaders'] = { 37 | 'Authorization': 'Bearer $accessToken' 38 | }; 39 | 40 | socket.onError((error) { 41 | if (context == null) { 42 | return; 43 | } 44 | 45 | showDialog( 46 | context: context, 47 | builder: (context) => AlertDialogWidget( 48 | title: error['error'] ?? error['message'], 49 | description: error['message'], 50 | ), 51 | ); 52 | }); 53 | 54 | final completer = Completer(); 55 | 56 | socket.onConnect((_) { 57 | socket.emit('user:subscribe'); 58 | 59 | if (onConnect != null) { 60 | onConnect(); 61 | } 62 | 63 | if (!completer.isCompleted) { 64 | completer.complete(socket); 65 | } 66 | }); 67 | 68 | socket.onDisconnect((reason) async { 69 | if (reason != 'io server disconnect') { 70 | return; 71 | } 72 | 73 | await repository.loginWithRefreshToken(); 74 | 75 | init(onConnect); 76 | 77 | dispose(); 78 | }); 79 | 80 | socket.connect(); 81 | 82 | return completer.future; 83 | } 84 | 85 | void dispose() { 86 | socket.dispose(); 87 | } 88 | } 89 | 90 | SocketManager socketManager = SocketManager(); 91 | -------------------------------------------------------------------------------- /lib/src/features/auth/logic/cubit/auth_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/models/user.dart'; 2 | import 'package:auth/src/features/auth/logic/repository/auth_repository.dart'; 3 | import 'package:auth/src/features/notification/logic/repository/notification_repository.dart'; 4 | import 'package:bloc/bloc.dart'; 5 | 6 | class AuthCubit extends Cubit { 7 | AuthRepository authRepository; 8 | 9 | AuthCubit({required this.authRepository}) : super(null); 10 | 11 | Future authenticate(String username, String password) async { 12 | return _loginWith(() => authRepository.authenticate(username, password)); 13 | } 14 | 15 | Future register(String username, String password, String email) async { 16 | return _loginWith(() => authRepository.register(username, password, email)); 17 | } 18 | 19 | Future loginWithFacebook() async { 20 | return _loginWith(() => authRepository.loginWithFacebook()); 21 | } 22 | 23 | Future loginWithGoogle() async { 24 | return _loginWith(() => authRepository.loginWithGoogle()); 25 | } 26 | 27 | Future logoutFromAllDevices() async { 28 | return _loginWith(() => authRepository.logoutFromAllDevices()); 29 | } 30 | 31 | Future loginWithApple() async { 32 | return _loginWith(() => authRepository.loginWithApple()); 33 | } 34 | 35 | Future _loginWith(Function method) async { 36 | await method(); 37 | 38 | return updateProfile(); 39 | } 40 | 41 | Future logout() async { 42 | emit(null); 43 | 44 | await authRepository.logout(); 45 | 46 | await notificationRepository.deleteSubscription(); 47 | } 48 | 49 | Future updateProfile() async { 50 | emit(await this.authRepository.getProfile()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/features/auth/logic/interceptors/auth_token_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/app.dart'; 2 | import 'package:auth/src/features/auth/logic/cubit/auth_cubit.dart'; 3 | import 'package:auth/src/features/auth/logic/models/tokens.dart'; 4 | import 'package:auth/src/features/auth/logic/repository/auth_repository.dart'; 5 | import 'package:dio/dio.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class AuthTokenInterceptor extends Interceptor { 10 | static const skipHeader = 'skipAuthToken'; 11 | 12 | Dio api; 13 | 14 | AuthTokenInterceptor(this.api); 15 | 16 | @override 17 | onRequest(RequestOptions options, RequestInterceptorHandler handler) async { 18 | final context = applicationKey.currentContext; 19 | 20 | final repository = context?.read(); 21 | 22 | if (repository == null) { 23 | return; 24 | } 25 | 26 | final accessToken = await repository.getAccessToken(); 27 | 28 | if (accessToken != null) { 29 | options.headers['Authorization'] = 'Bearer $accessToken'; 30 | } 31 | 32 | return super.onRequest(options, handler); 33 | } 34 | 35 | @override 36 | onError(DioError err, ErrorInterceptorHandler handler) async { 37 | final context = applicationKey.currentContext; 38 | 39 | if (context == null) { 40 | return; 41 | } 42 | 43 | final response = err.response?.data; 44 | 45 | if (response == null) { 46 | return super.onError(err, handler); 47 | } 48 | 49 | final repository = context.read(); 50 | 51 | if (err.response?.statusCode == 401 && 52 | await repository.getRefreshToken() != null) { 53 | return _handlerRefreshToken(context, repository, err, handler); 54 | } 55 | 56 | return super.onError(err, handler); 57 | } 58 | 59 | _handlerRefreshToken( 60 | BuildContext context, 61 | AuthRepository repository, 62 | DioError err, 63 | ErrorInterceptorHandler handler, 64 | ) async { 65 | final requestOptions = err.requestOptions; 66 | 67 | if (requestOptions.headers.containsKey(skipHeader)) { 68 | return super.onError(err, handler); 69 | } 70 | 71 | final refreshToken = await repository.getRefreshToken(); 72 | 73 | try { 74 | final response = await api.post( 75 | '/auth/refresh-token', 76 | data: { 77 | 'refreshToken': refreshToken, 78 | }, 79 | options: Options( 80 | headers: { 81 | skipHeader: true, 82 | }, 83 | ), 84 | ); 85 | 86 | final tokens = Tokens.fromJson(response.data); 87 | 88 | await repository.setTokens(tokens); 89 | 90 | try { 91 | final headers = requestOptions.headers; 92 | 93 | headers[skipHeader] = true; 94 | 95 | final finalResponse = await api.request( 96 | requestOptions.path, 97 | cancelToken: requestOptions.cancelToken, 98 | data: requestOptions.data, 99 | onReceiveProgress: requestOptions.onReceiveProgress, 100 | onSendProgress: requestOptions.onSendProgress, 101 | queryParameters: requestOptions.queryParameters, 102 | options: Options( 103 | method: requestOptions.method, 104 | headers: headers, 105 | ), 106 | ); 107 | 108 | return handler.resolve(finalResponse); 109 | } on DioError catch (e) { 110 | return handler.next(e); 111 | } catch (e) { 112 | return super.onError(err, handler); 113 | } 114 | } catch (e) { 115 | await context.read().logout(); 116 | 117 | return super.onError(err, handler); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/src/features/auth/logic/models/tokens.dart: -------------------------------------------------------------------------------- 1 | class Tokens { 2 | late final String accessToken; 3 | late final String refreshToken; 4 | 5 | Tokens({required this.accessToken}); 6 | 7 | Tokens.fromJson(Map json) { 8 | accessToken = json['access_token']; 9 | refreshToken = json['refresh_token']; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/features/auth/logic/models/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class User extends Equatable implements Comparable { 4 | late final String id; 5 | late final String username; 6 | late final String? email; 7 | late final bool online; 8 | late final bool isSocial; 9 | 10 | User({ 11 | required this.id, 12 | required this.username, 13 | required this.email, 14 | required this.online, 15 | this.isSocial = false, 16 | }); 17 | 18 | User.fromJson(Map json) { 19 | id = json['_id']; 20 | username = json['username'] ?? ''; 21 | email = json['email']; 22 | online = json['online'] ?? false; 23 | isSocial = json['isSocial'] ?? false; 24 | } 25 | 26 | static List fromList(List list) { 27 | return list.map((e) => User.fromJson(e)).toList(); 28 | } 29 | 30 | @override 31 | int compareTo(dynamic other) { 32 | if (this.online == other.online) { 33 | return 0; 34 | } 35 | 36 | if (this.online) { 37 | return -1; 38 | } 39 | 40 | if (other.online) { 41 | return 1; 42 | } 43 | 44 | return 0; 45 | } 46 | 47 | @override 48 | List get props => [id, username, email, online]; 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/features/auth/logic/provider/auth_api_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/interceptors/auth_token_interceptor.dart'; 2 | import 'package:auth/src/features/auth/logic/models/tokens.dart'; 3 | import 'package:auth/src/features/auth/logic/models/user.dart'; 4 | import 'package:auth/src/shared/logic/http/api.dart'; 5 | import 'package:auth/src/shared/logic/http/interceptors/error_dialog_interceptor.dart'; 6 | 7 | class AuthAPIProvider { 8 | Future authenticate(String username, String password) async { 9 | final response = await api.post( 10 | '/auth/login', 11 | data: { 12 | 'username': username, 13 | 'password': password, 14 | }, 15 | ); 16 | 17 | final tokens = Tokens.fromJson(response.data); 18 | 19 | return tokens; 20 | } 21 | 22 | Future register( 23 | String username, 24 | String password, 25 | String email, 26 | ) async { 27 | final response = await api.post( 28 | '/auth/register', 29 | data: { 30 | 'username': username, 31 | 'password': password, 32 | 'email': email, 33 | }, 34 | ); 35 | 36 | final tokens = Tokens.fromJson(response.data); 37 | 38 | return tokens; 39 | } 40 | 41 | Future recover(String email) async { 42 | await api.post( 43 | '/recover', 44 | data: { 45 | 'email': email, 46 | }, 47 | ); 48 | } 49 | 50 | Future getProfile() async { 51 | final response = await api.get( 52 | '/auth/me', 53 | options: Options( 54 | headers: { 55 | ErrorDialogInterceptor.skipHeader: true, 56 | }, 57 | ), 58 | ); 59 | 60 | return User.fromJson(response.data); 61 | } 62 | 63 | Future loginWithFacebook(String? accessToken) { 64 | return _socialLogin( 65 | provider: 'facebook', 66 | accessToken: accessToken, 67 | ); 68 | } 69 | 70 | Future loginWithGoogle(String? accessToken) { 71 | return _socialLogin( 72 | provider: 'google', 73 | accessToken: accessToken, 74 | ); 75 | } 76 | 77 | Future loginWithApple({ 78 | required String? identityToken, 79 | required String authorizationCode, 80 | String? givenName, 81 | String? familyName, 82 | String? type, 83 | }) { 84 | return _socialLogin( 85 | provider: 'apple', 86 | accessToken: identityToken, 87 | authorizationCode: authorizationCode, 88 | type: type, 89 | name: '$givenName $familyName', 90 | ); 91 | } 92 | 93 | Future _socialLogin({ 94 | required String provider, 95 | required String? accessToken, 96 | String? authorizationCode, 97 | String? type, 98 | String? name, 99 | }) async { 100 | final response = await api.post( 101 | '/auth/$provider-login', 102 | data: { 103 | 'name': name, 104 | 'accessToken': accessToken, 105 | 'authorizationCode': authorizationCode, 106 | 'type': type, 107 | }, 108 | ); 109 | 110 | return Tokens.fromJson(response.data); 111 | } 112 | 113 | Future loginWithRefreshToken(String? refreshToken) async { 114 | final response = await api.post( 115 | '/auth/refresh-token', 116 | data: {'refreshToken': refreshToken}, 117 | options: Options(headers: {AuthTokenInterceptor.skipHeader: true}), 118 | ); 119 | 120 | return Tokens.fromJson(response.data); 121 | } 122 | 123 | Future logoutFromAllDevices() async { 124 | final response = await api.delete('/auth/logout-from-all-devices'); 125 | 126 | return Tokens.fromJson(response.data); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/src/features/auth/logic/repository/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:auth/src/constants/environments.dart'; 5 | import 'package:auth/src/features/auth/logic/models/tokens.dart'; 6 | import 'package:auth/src/features/auth/logic/models/user.dart'; 7 | import 'package:auth/src/features/auth/logic/provider/auth_api_provider.dart'; 8 | import 'package:flutter_facebook_auth/flutter_facebook_auth.dart'; 9 | import 'package:flutter_secure_storage/flutter_secure_storage.dart' as store; 10 | import 'package:google_sign_in/google_sign_in.dart'; 11 | import 'package:sign_in_with_apple/sign_in_with_apple.dart'; 12 | 13 | class AuthRepository { 14 | final _provider = AuthAPIProvider(); 15 | late final _storage = new store.FlutterSecureStorage(); 16 | 17 | late final _googleSignIn = GoogleSignIn( 18 | scopes: ['email', 'profile'], 19 | ); 20 | 21 | String getDeviceType() { 22 | String type = 'web'; 23 | 24 | if (Platform.isIOS) { 25 | type = 'ios'; 26 | } 27 | 28 | if (Platform.isAndroid) { 29 | type = 'android'; 30 | } 31 | 32 | return type; 33 | } 34 | 35 | Future logout() async { 36 | await deleteAccessToken(); 37 | await deleteRefreshToken(); 38 | } 39 | 40 | Future getProfile() async { 41 | if (await getAccessToken() == null && await getRefreshToken() == null) { 42 | return null; 43 | } 44 | 45 | try { 46 | final profile = await _provider.getProfile(); 47 | 48 | return profile; 49 | } catch (e) { 50 | return null; 51 | } 52 | } 53 | 54 | Future authenticate(String username, String password) async { 55 | return setTokens( 56 | await _provider.authenticate(username, password), 57 | ); 58 | } 59 | 60 | Future register(String username, String password, String email) async { 61 | return setTokens( 62 | await _provider.register(username, password, email), 63 | ); 64 | } 65 | 66 | Future loginWithRefreshToken() async { 67 | return setTokens( 68 | await _provider.loginWithRefreshToken(await getRefreshToken()), 69 | ); 70 | } 71 | 72 | Future loginWithFacebook() async { 73 | await FacebookAuth.instance.logOut(); 74 | 75 | final result = await FacebookAuth.instance.login(); 76 | 77 | if (result.status == LoginStatus.success) { 78 | return setTokens( 79 | await _provider.loginWithFacebook(result.accessToken?.token), 80 | ); 81 | } else { 82 | throw Exception(result.message); 83 | } 84 | } 85 | 86 | Future loginWithApple() async { 87 | if (!await SignInWithApple.isAvailable()) { 88 | throw new Exception('Apple login is not available'); 89 | } 90 | 91 | final result = await SignInWithApple.getAppleIDCredential( 92 | scopes: [ 93 | AppleIDAuthorizationScopes.email, 94 | AppleIDAuthorizationScopes.fullName, 95 | ], 96 | webAuthenticationOptions: WebAuthenticationOptions( 97 | clientId: environments.appleSignInClientId, 98 | redirectUri: environments.appleSignInRedirectUri, 99 | ), 100 | ); 101 | 102 | return setTokens( 103 | await _provider.loginWithApple( 104 | identityToken: result.identityToken, 105 | authorizationCode: result.authorizationCode, 106 | givenName: result.givenName, 107 | familyName: result.familyName, 108 | type: getDeviceType(), 109 | ), 110 | ); 111 | } 112 | 113 | Future loginWithGoogle() async { 114 | await _googleSignIn.signOut(); 115 | 116 | final result = await _googleSignIn.signIn(); 117 | 118 | if (result == null) { 119 | throw Exception('An error occurred authenticating with Google'); 120 | } 121 | 122 | final authentication = await result.authentication; 123 | 124 | return setTokens( 125 | await _provider.loginWithGoogle(authentication.accessToken), 126 | ); 127 | } 128 | 129 | Future logoutFromAllDevices() async { 130 | return setTokens(await _provider.logoutFromAllDevices()); 131 | } 132 | 133 | Future recover(String email) { 134 | return _provider.recover(email); 135 | } 136 | 137 | Future getAccessToken() { 138 | return _storage.read(key: 'accessToken'); 139 | } 140 | 141 | Future setAccessToken(String token) { 142 | return _storage.write(key: 'accessToken', value: token); 143 | } 144 | 145 | Future deleteAccessToken() { 146 | return _storage.delete(key: 'accessToken'); 147 | } 148 | 149 | Future getRefreshToken() { 150 | return _storage.read(key: 'refreshToken'); 151 | } 152 | 153 | Future setRefreshToken(String token) { 154 | return _storage.write(key: 'refreshToken', value: token); 155 | } 156 | 157 | Future deleteRefreshToken() { 158 | return _storage.delete(key: 'refreshToken'); 159 | } 160 | 161 | Future setTokens(Tokens tokens) async { 162 | await setAccessToken(tokens.accessToken); 163 | await setRefreshToken(tokens.refreshToken); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /lib/src/features/auth/views/screens/recover_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/repository/auth_repository.dart'; 2 | import 'package:auth/src/features/auth/views/screens/login_screen.dart'; 3 | import 'package:auth/src/features/auth/views/screens/register_screen.dart'; 4 | import 'package:auth/src/shared/views/widgets/dialog/alert_dialog_widget.dart'; 5 | import 'package:auth/src/shared/views/widgets/circles_background.dart'; 6 | import 'package:auth/src/shared/views/widgets/go_back_button.dart'; 7 | import 'package:auth/src/shared/views/widgets/main_text_field.dart'; 8 | import 'package:auth/src/shared/views/widgets/next_button.dart'; 9 | import 'package:auth/src/shared/views/widgets/scrollable_form.dart'; 10 | import 'package:auth/src/shared/views/widgets/underlined_button.dart'; 11 | import 'package:flutter/material.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | class RecoverScreen extends StatefulWidget { 15 | static const routeName = '/recover'; 16 | 17 | static route() => MaterialPageRoute(builder: (_) => RecoverScreen()); 18 | 19 | RecoverScreen({Key? key}) : super(key: key); 20 | 21 | @override 22 | _RecoverScreenState createState() => _RecoverScreenState(); 23 | } 24 | 25 | class _RecoverScreenState extends State { 26 | final _emailController = TextEditingController(); 27 | 28 | bool _loading = false; 29 | 30 | @override 31 | void dispose() { 32 | super.dispose(); 33 | 34 | _emailController.dispose(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | final theme = Theme.of(context); 40 | 41 | final node = FocusScope.of(context); 42 | 43 | return Scaffold( 44 | body: CirclesBackground( 45 | backgroundColor: Colors.white, 46 | topSmallCircleColor: theme.secondaryHeaderColor, 47 | topMediumCircleColor: theme.primaryColor, 48 | topRightCircleColor: theme.highlightColor, 49 | bottomRightCircleColor: Colors.white, 50 | child: Stack( 51 | children: [ 52 | GoBackButton(), 53 | Column( 54 | children: [ 55 | ScrollableForm( 56 | padding: EdgeInsets.symmetric(horizontal: 40), 57 | children: [ 58 | SizedBox( 59 | height: 90, 60 | ), 61 | ConstrainedBox( 62 | constraints: BoxConstraints(maxWidth: 250), 63 | child: Text( 64 | 'Forgot Password', 65 | style: TextStyle( 66 | fontSize: 46, 67 | fontWeight: FontWeight.bold, 68 | color: Colors.white, 69 | ), 70 | ), 71 | ), 72 | SizedBox( 73 | height: 70, 74 | ), 75 | _form(node, context), 76 | ], 77 | ), 78 | _FooterButtons(), 79 | ], 80 | ), 81 | ], 82 | ), 83 | ), 84 | ); 85 | } 86 | 87 | Widget _form(FocusScopeNode node, BuildContext context) { 88 | return Column( 89 | crossAxisAlignment: CrossAxisAlignment.start, 90 | children: [ 91 | MainTextField( 92 | label: 'Email', 93 | controller: _emailController, 94 | emailField: true, 95 | onSubmitted: (_) { 96 | node.unfocus(); 97 | 98 | _recover(context); 99 | }, 100 | ), 101 | SizedBox( 102 | height: 20, 103 | ), 104 | Row( 105 | children: [ 106 | Text( 107 | 'Recover', 108 | style: TextStyle( 109 | fontWeight: FontWeight.bold, 110 | fontSize: 30, 111 | ), 112 | ), 113 | Spacer(), 114 | NextButton( 115 | onPressed: () => _recover(context), 116 | loading: _loading, 117 | ) 118 | ], 119 | ), 120 | ], 121 | ); 122 | } 123 | 124 | _recover(BuildContext context) async { 125 | if (_loading) { 126 | return; 127 | } 128 | 129 | final repository = context.read(); 130 | 131 | setState(() { 132 | _loading = true; 133 | }); 134 | 135 | try { 136 | await repository.recover( 137 | _emailController.text, 138 | ); 139 | 140 | _emailController.text = ''; 141 | 142 | showDialog( 143 | context: context, 144 | builder: (context) => AlertDialogWidget( 145 | title: 'Success', 146 | description: 'Check your email and change your password!', 147 | ), 148 | ); 149 | } finally { 150 | setState(() { 151 | _loading = false; 152 | }); 153 | } 154 | } 155 | } 156 | 157 | class _FooterButtons extends StatelessWidget { 158 | const _FooterButtons({Key? key}) : super(key: key); 159 | 160 | @override 161 | Widget build(BuildContext context) { 162 | final theme = Theme.of(context); 163 | 164 | return Padding( 165 | padding: EdgeInsets.symmetric(horizontal: 40), 166 | child: Row( 167 | children: [ 168 | UnderlinedButton( 169 | child: Text('Sign In'), 170 | color: theme.secondaryHeaderColor, 171 | onPressed: () => Navigator.pushNamed( 172 | context, 173 | LoginScreen.routeName, 174 | ), 175 | ), 176 | Spacer(), 177 | UnderlinedButton( 178 | child: Text('Sign Up'), 179 | color: theme.highlightColor, 180 | onPressed: () => Navigator.pushNamed( 181 | context, 182 | RegisterScreen.routeName, 183 | ), 184 | ), 185 | ], 186 | ), 187 | ); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /lib/src/features/auth/views/screens/register_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/cubit/auth_cubit.dart'; 2 | import 'package:auth/src/features/auth/views/screens/login_screen.dart'; 3 | import 'package:auth/src/features/home/views/screens/home_screen.dart'; 4 | import 'package:auth/src/shared/views/widgets/circles_background.dart'; 5 | import 'package:auth/src/shared/views/widgets/go_back_button.dart'; 6 | import 'package:auth/src/shared/views/widgets/main_text_field.dart'; 7 | import 'package:auth/src/shared/views/widgets/next_button.dart'; 8 | import 'package:auth/src/shared/views/widgets/scrollable_form.dart'; 9 | import 'package:auth/src/shared/views/widgets/underlined_button.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_signin_button/flutter_signin_button.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | class RegisterScreen extends StatefulWidget { 15 | static const routeName = '/register'; 16 | 17 | static route() => MaterialPageRoute(builder: (_) => RegisterScreen()); 18 | 19 | RegisterScreen({Key? key}) : super(key: key); 20 | 21 | @override 22 | _RegisterScreenState createState() => _RegisterScreenState(); 23 | } 24 | 25 | class _RegisterScreenState extends State { 26 | final _passwordController = TextEditingController(); 27 | 28 | String _username = ''; 29 | String _email = ''; 30 | bool _loading = false; 31 | 32 | @override 33 | void dispose() { 34 | super.dispose(); 35 | 36 | _passwordController.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | final theme = Theme.of(context); 42 | 43 | final node = FocusScope.of(context); 44 | 45 | return Scaffold( 46 | body: CirclesBackground( 47 | backgroundColor: theme.highlightColor, 48 | topSmallCircleColor: theme.primaryColor, 49 | topMediumCircleColor: theme.primaryColor, 50 | topRightCircleColor: theme.highlightColor, 51 | bottomRightCircleColor: Colors.white, 52 | child: Stack( 53 | children: [ 54 | GoBackButton(), 55 | Column( 56 | children: [ 57 | ScrollableForm( 58 | padding: EdgeInsets.symmetric(horizontal: 40), 59 | children: [ 60 | SizedBox( 61 | height: 90, 62 | ), 63 | Column( 64 | crossAxisAlignment: CrossAxisAlignment.start, 65 | children: [ 66 | ConstrainedBox( 67 | constraints: BoxConstraints(maxWidth: 250), 68 | child: Text( 69 | 'Create Account', 70 | style: TextStyle( 71 | fontSize: 46, 72 | fontWeight: FontWeight.bold, 73 | color: Colors.white, 74 | ), 75 | ), 76 | ), 77 | SizedBox( 78 | height: 70, 79 | ), 80 | _form(node, context), 81 | _thirdPartySignInButtons(context), 82 | ], 83 | ), 84 | ], 85 | ), 86 | _FooterButtons(), 87 | ], 88 | ), 89 | ], 90 | ), 91 | ), 92 | ); 93 | } 94 | 95 | Widget _form(FocusScopeNode node, BuildContext context) { 96 | return Column( 97 | crossAxisAlignment: CrossAxisAlignment.start, 98 | children: [ 99 | MainTextField( 100 | label: 'Username', 101 | usernameField: true, 102 | onChanged: (value) => setState(() { 103 | _username = value; 104 | }), 105 | onEditingComplete: () => node.nextFocus(), 106 | ), 107 | SizedBox( 108 | height: 20, 109 | ), 110 | MainTextField( 111 | label: 'Email', 112 | emailField: true, 113 | onChanged: (value) => setState(() { 114 | _email = value; 115 | }), 116 | onEditingComplete: () => node.nextFocus(), 117 | ), 118 | SizedBox( 119 | height: 20, 120 | ), 121 | MainTextField( 122 | label: 'Password', 123 | controller: _passwordController, 124 | passwordField: true, 125 | onSubmitted: (_) { 126 | node.unfocus(); 127 | 128 | _registerWithAccount(context); 129 | }, 130 | ), 131 | SizedBox( 132 | height: 20, 133 | ), 134 | Row( 135 | children: [ 136 | Text( 137 | 'Sign Up', 138 | style: TextStyle( 139 | fontWeight: FontWeight.bold, 140 | fontSize: 30, 141 | ), 142 | ), 143 | Spacer(), 144 | NextButton( 145 | onPressed: () => _registerWithAccount(context), 146 | loading: _loading, 147 | ) 148 | ], 149 | ), 150 | ], 151 | ); 152 | } 153 | 154 | Widget _thirdPartySignInButtons(BuildContext context) { 155 | return Column( 156 | children: [ 157 | SizedBox( 158 | height: 30, 159 | ), 160 | SignInButton( 161 | Buttons.AppleDark, 162 | text: "Sign up with Apple", 163 | onPressed: () => _registerWithApple(context), 164 | ), 165 | SizedBox( 166 | height: 10, 167 | ), 168 | SignInButton( 169 | Buttons.Facebook, 170 | text: "Sign up with Facebook", 171 | onPressed: () => _registerWithFacebook(context), 172 | ), 173 | SizedBox( 174 | height: 10, 175 | ), 176 | SignInButton( 177 | Buttons.GoogleDark, 178 | text: "Sign up with Google", 179 | onPressed: () => _registerWithGoogle(context), 180 | ) 181 | ], 182 | ); 183 | } 184 | 185 | _registerWithAccount(BuildContext context) async { 186 | final bloc = context.read(); 187 | 188 | return _registerWith(context, () { 189 | return bloc.register( 190 | _username, 191 | _passwordController.text, 192 | _email, 193 | ); 194 | }); 195 | } 196 | 197 | _registerWithFacebook(BuildContext context) { 198 | final bloc = context.read(); 199 | 200 | return _registerWith(context, () => bloc.loginWithFacebook()); 201 | } 202 | 203 | _registerWithGoogle(BuildContext context) { 204 | final bloc = context.read(); 205 | 206 | return _registerWith(context, () => bloc.loginWithGoogle()); 207 | } 208 | 209 | _registerWithApple(BuildContext context) { 210 | final bloc = context.read(); 211 | 212 | return _registerWith(context, () => bloc.loginWithApple()); 213 | } 214 | 215 | _registerWith(BuildContext context, Future Function() method) async { 216 | if (_loading) { 217 | return; 218 | } 219 | 220 | setState(() { 221 | _loading = true; 222 | }); 223 | 224 | try { 225 | await method(); 226 | 227 | Navigator.pushNamedAndRemoveUntil( 228 | context, 229 | HomeScreen.routeName, 230 | (route) => false, 231 | ); 232 | } finally { 233 | setState(() { 234 | _loading = false; 235 | }); 236 | 237 | _passwordController.text = ''; 238 | } 239 | } 240 | } 241 | 242 | class _FooterButtons extends StatelessWidget { 243 | const _FooterButtons({Key? key}) : super(key: key); 244 | 245 | @override 246 | Widget build(BuildContext context) { 247 | final theme = Theme.of(context); 248 | 249 | return Padding( 250 | padding: EdgeInsets.symmetric(horizontal: 40), 251 | child: Row( 252 | children: [ 253 | Spacer(), 254 | UnderlinedButton( 255 | onPressed: () => Navigator.pushNamed( 256 | context, 257 | LoginScreen.routeName, 258 | ), 259 | child: Text('Sign In'), 260 | color: theme.highlightColor, 261 | ), 262 | ], 263 | ), 264 | ); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /lib/src/features/home/views/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/cubit/auth_cubit.dart'; 2 | import 'package:auth/src/features/auth/logic/models/user.dart'; 3 | import 'package:auth/src/features/home/views/widgets/authenticated_home.dart'; 4 | import 'package:auth/src/features/home/views/widgets/non_authenticated_home.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class HomeScreen extends StatelessWidget { 10 | static const routeName = '/'; 11 | 12 | static route() => MaterialPageRoute(builder: (_) => HomeScreen()); 13 | 14 | HomeScreen({Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final theme = Theme.of(context); 19 | 20 | final bloc = context.read(); 21 | 22 | return Scaffold( 23 | body: FutureBuilder( 24 | future: bloc.updateProfile(), 25 | builder: (BuildContext context, AsyncSnapshot snapshot) { 26 | if (snapshot.connectionState == ConnectionState.done) { 27 | return BlocBuilder( 28 | buildWhen: (prev, curr) => prev?.id != curr?.id, 29 | builder: (context, user) { 30 | return user != null 31 | ? AuthenticatedHome(user: user) 32 | : NonAuthenticatedHome(); 33 | }, 34 | ); 35 | } 36 | 37 | return Center( 38 | child: CircularProgressIndicator( 39 | color: theme.primaryColor, 40 | ), 41 | ); 42 | }, 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/features/home/views/widgets/authenticated_home.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/cubit/auth_cubit.dart'; 2 | import 'package:auth/src/features/auth/logic/models/user.dart'; 3 | import 'package:auth/src/features/notification/views/widgets/notification_handler.dart'; 4 | import 'package:auth/src/features/room/views/screens/rooms_screen.dart'; 5 | import 'package:auth/src/features/settings/views/screens/settings_screen.dart'; 6 | import 'package:auth/src/shared/views/widgets/circles_background.dart'; 7 | import 'package:auth/src/shared/views/widgets/underlined_button.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:provider/provider.dart'; 10 | 11 | class AuthenticatedHome extends StatelessWidget { 12 | final User user; 13 | 14 | AuthenticatedHome({ 15 | Key? key, 16 | required this.user, 17 | }) : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | final theme = Theme.of(context); 22 | 23 | return CirclesBackground( 24 | backgroundColor: Colors.white, 25 | topSmallCircleColor: theme.secondaryHeaderColor, 26 | topMediumCircleColor: theme.primaryColor, 27 | topRightCircleColor: Colors.white, 28 | bottomRightCircleColor: theme.highlightColor, 29 | child: Container( 30 | padding: EdgeInsets.symmetric(horizontal: 40), 31 | child: Column( 32 | crossAxisAlignment: CrossAxisAlignment.start, 33 | children: [ 34 | NotificationHandler(), 35 | SizedBox( 36 | height: 80, 37 | ), 38 | ConstrainedBox( 39 | constraints: BoxConstraints(maxWidth: 300), 40 | child: Text( 41 | 'Authentication Application', 42 | style: TextStyle( 43 | fontSize: 40, 44 | fontWeight: FontWeight.bold, 45 | color: Colors.white, 46 | ), 47 | ), 48 | ), 49 | Spacer(), 50 | Column( 51 | crossAxisAlignment: CrossAxisAlignment.start, 52 | children: [ 53 | Text( 54 | 'Authenticated as ', 55 | style: TextStyle( 56 | fontWeight: FontWeight.bold, 57 | fontSize: 30, 58 | ), 59 | ), 60 | Text( 61 | user.username, 62 | style: TextStyle( 63 | fontWeight: FontWeight.bold, 64 | color: theme.highlightColor, 65 | fontSize: 30, 66 | ), 67 | ), 68 | Text( 69 | '(${user.email})', 70 | style: TextStyle( 71 | fontWeight: FontWeight.bold, 72 | color: theme.highlightColor, 73 | ), 74 | ), 75 | Row( 76 | children: [ 77 | UnderlinedButton( 78 | child: Text('Logout'), 79 | color: theme.secondaryHeaderColor, 80 | onPressed: () => context.read().logout(), 81 | ), 82 | UnderlinedButton( 83 | child: Text('Rooms'), 84 | color: theme.highlightColor, 85 | onPressed: () => Navigator.pushNamed( 86 | context, 87 | RoomsScreen.routeName, 88 | ), 89 | ), 90 | UnderlinedButton( 91 | child: Text('Settings'), 92 | color: theme.secondaryHeaderColor, 93 | onPressed: () => Navigator.pushNamed( 94 | context, 95 | SettingsScreen.routeName, 96 | ), 97 | ) 98 | ], 99 | ), 100 | ], 101 | ), 102 | Spacer(), 103 | Row( 104 | children: [ 105 | Spacer(), 106 | UnderlinedButton( 107 | child: Text('Logout'), 108 | color: theme.highlightColor, 109 | onPressed: () => context.read().logout(), 110 | ), 111 | ], 112 | ), 113 | ], 114 | ), 115 | ), 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/src/features/home/views/widgets/non_authenticated_home.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/views/screens/login_screen.dart'; 2 | import 'package:auth/src/features/auth/views/screens/register_screen.dart'; 3 | import 'package:auth/src/shared/views/widgets/circles_background.dart'; 4 | import 'package:auth/src/shared/views/widgets/underlined_button.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class NonAuthenticatedHome extends StatelessWidget { 8 | const NonAuthenticatedHome({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final theme = Theme.of(context); 13 | 14 | return CirclesBackground( 15 | backgroundColor: Colors.white, 16 | topSmallCircleColor: theme.secondaryHeaderColor, 17 | topMediumCircleColor: theme.primaryColor, 18 | topRightCircleColor: Colors.white, 19 | bottomRightCircleColor: theme.highlightColor, 20 | child: Container( 21 | padding: EdgeInsets.symmetric(horizontal: 40), 22 | child: Column( 23 | crossAxisAlignment: CrossAxisAlignment.start, 24 | children: [ 25 | SizedBox( 26 | height: 80, 27 | ), 28 | ConstrainedBox( 29 | constraints: BoxConstraints(maxWidth: 300), 30 | child: Text( 31 | 'Authentication Application', 32 | style: TextStyle( 33 | fontSize: 40, 34 | fontWeight: FontWeight.bold, 35 | color: Colors.white, 36 | ), 37 | ), 38 | ), 39 | Spacer(), 40 | Row( 41 | children: [ 42 | Text( 43 | 'by ', 44 | style: TextStyle( 45 | fontWeight: FontWeight.bold, 46 | fontSize: 30, 47 | ), 48 | ), 49 | Text( 50 | 'Denzel Code', 51 | style: TextStyle( 52 | fontWeight: FontWeight.bold, 53 | color: theme.highlightColor, 54 | fontSize: 30, 55 | ), 56 | ) 57 | ], 58 | ), 59 | Spacer(), 60 | Row( 61 | children: [ 62 | UnderlinedButton( 63 | child: Text('Sign In'), 64 | color: theme.secondaryHeaderColor, 65 | onPressed: () => Navigator.pushNamed( 66 | context, 67 | LoginScreen.routeName, 68 | ), 69 | ), 70 | Spacer(), 71 | UnderlinedButton( 72 | child: Text('Sign Up'), 73 | color: theme.highlightColor, 74 | onPressed: () => Navigator.pushNamed( 75 | context, 76 | RegisterScreen.routeName, 77 | ), 78 | ), 79 | ], 80 | ), 81 | ], 82 | ), 83 | ), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/bloc/direct_message_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:auth/src/core/socket.dart'; 4 | import 'package:auth/src/features/auth/logic/models/user.dart'; 5 | import 'package:auth/src/features/user/logic/repository/user_repository.dart'; 6 | import 'package:bloc/bloc.dart'; 7 | import 'package:equatable/equatable.dart'; 8 | import 'package:socket_io_client/socket_io_client.dart'; 9 | 10 | part 'direct_message_event.dart'; 11 | part 'direct_message_state.dart'; 12 | 13 | class DirectMessageBloc extends Bloc { 14 | final String username; 15 | 16 | final _userRepository = UserRepository(); 17 | 18 | final Socket socket = socketManager.socket; 19 | 20 | Timer? updateTimer; 21 | 22 | final bool fromMessages; 23 | 24 | DirectMessageBloc(this.username, {this.fromMessages = false}) 25 | : super(DirectMessageInitialState()) { 26 | initEvents(); 27 | 28 | initTimers(); 29 | } 30 | 31 | void initSockets() { 32 | socket.onConnect((_) => add(SocketConnectedEvent())); 33 | 34 | socket.onDisconnect((_) => add(SocketDisconnectedEvent())); 35 | 36 | if (socket.connected) { 37 | add(SocketConnectedEvent()); 38 | } else { 39 | socketManager.init(); 40 | } 41 | } 42 | 43 | void initEvents() { 44 | on(_onLoaded); 45 | on(_onConnected); 46 | on(_onDisconnected); 47 | } 48 | 49 | void initTimers() { 50 | updateTimer = Timer.periodic( 51 | Duration(seconds: 5), 52 | (_) => add(UserLoadedEvent()), 53 | ); 54 | } 55 | 56 | FutureOr _onLoaded( 57 | UserLoadedEvent event, 58 | Emitter emit, 59 | ) async { 60 | if (state is UserLoadInProgressState) { 61 | return; 62 | } 63 | 64 | final isInitial = state is DirectMessageInitialState; 65 | 66 | if (isInitial) { 67 | emit.call(UserLoadInProgressState()); 68 | } 69 | 70 | try { 71 | final user = await _userRepository.getUser(username); 72 | 73 | if (state is SocketConnectState) { 74 | emit.call(SocketConnectState(user)); 75 | } else { 76 | emit.call(UserLoadSuccessState(user)); 77 | 78 | if (isInitial) { 79 | initSockets(); 80 | } 81 | } 82 | } catch (_) { 83 | emit.call(UserLoadFailureState()); 84 | } 85 | } 86 | 87 | @override 88 | Future close() { 89 | updateTimer?.cancel(); 90 | 91 | if (!fromMessages) { 92 | socketManager.dispose(); 93 | } 94 | 95 | return super.close(); 96 | } 97 | 98 | FutureOr _onConnected( 99 | SocketConnectedEvent event, 100 | Emitter emit, 101 | ) { 102 | if (!(state is DirectUserState)) { 103 | return null; 104 | } 105 | 106 | final data = state as DirectUserState; 107 | 108 | emit.call(SocketConnectState(data.user)); 109 | } 110 | 111 | FutureOr _onDisconnected( 112 | SocketDisconnectedEvent event, 113 | Emitter emit, 114 | ) { 115 | if (!(state is DirectUserState)) { 116 | return null; 117 | } 118 | 119 | final data = state as DirectUserState; 120 | 121 | emit.call(SocketDisconnectState(data.user)); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/bloc/direct_message_event.dart: -------------------------------------------------------------------------------- 1 | part of 'direct_message_bloc.dart'; 2 | 3 | abstract class DirectMessageEvent extends Equatable { 4 | const DirectMessageEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class UserLoadedEvent extends DirectMessageEvent {} 11 | 12 | class SocketDisconnectedEvent extends DirectMessageEvent {} 13 | 14 | class SocketConnectedEvent extends DirectMessageEvent {} 15 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/bloc/direct_message_state.dart: -------------------------------------------------------------------------------- 1 | part of 'direct_message_bloc.dart'; 2 | 3 | abstract class DirectMessageState extends Equatable { 4 | const DirectMessageState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class DirectMessageInitialState extends DirectMessageState {} 11 | 12 | class UserLoadInProgressState extends DirectMessageState {} 13 | 14 | class UserLoadFailureState extends DirectMessageState {} 15 | 16 | abstract class DirectUserState extends DirectMessageState { 17 | final User user; 18 | 19 | DirectUserState(this.user) : super(); 20 | 21 | @override 22 | List get props => [user]; 23 | } 24 | 25 | class UserLoadSuccessState extends DirectUserState { 26 | UserLoadSuccessState(User user) : super(user); 27 | } 28 | 29 | class SocketConnectState extends DirectUserState { 30 | SocketConnectState(User user) : super(user); 31 | } 32 | 33 | class SocketDisconnectState extends DirectUserState { 34 | SocketDisconnectState(User user) : super(user); 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/bloc/message_event.dart: -------------------------------------------------------------------------------- 1 | part of 'message_bloc.dart'; 2 | 3 | abstract class MessageEvent extends Equatable { 4 | const MessageEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class MessagesLoadedEvent extends MessageEvent {} 11 | 12 | class PreviousMessagesLoadedEvent extends MessageEvent { 13 | final double previousScrollHeight; 14 | 15 | PreviousMessagesLoadedEvent(this.previousScrollHeight); 16 | } 17 | 18 | class MessageSentEvent extends MessageEvent { 19 | final String message; 20 | 21 | MessageSentEvent(this.message) : super(); 22 | 23 | @override 24 | List get props => [message]; 25 | } 26 | 27 | abstract class MessageObjectEvent extends MessageEvent { 28 | final Message message; 29 | 30 | MessageObjectEvent(this.message) : super(); 31 | 32 | @override 33 | List get props => [message]; 34 | } 35 | 36 | class MessageDeletedEvent extends MessageEvent { 37 | final String messageId; 38 | 39 | MessageDeletedEvent(this.messageId) : super(); 40 | 41 | @override 42 | List get props => [messageId]; 43 | } 44 | 45 | class MessageDeletedRequestEvent extends MessageEvent { 46 | final Message message; 47 | 48 | MessageDeletedRequestEvent(this.message) : super(); 49 | 50 | @override 51 | List get props => [message]; 52 | } 53 | 54 | class MessagesDeletedEvent extends MessageEvent {} 55 | 56 | class TypingRemovedEvent extends MessageEvent { 57 | final User user; 58 | 59 | TypingRemovedEvent(this.user); 60 | } 61 | 62 | class MessageReceivedEvent extends MessageObjectEvent { 63 | MessageReceivedEvent(Message message) : super(message); 64 | } 65 | 66 | class MessageUserTypedEvent extends MessageEvent { 67 | final Typing typing; 68 | 69 | MessageUserTypedEvent(this.typing) : super(); 70 | 71 | @override 72 | List get props => [typing]; 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/bloc/message_state.dart: -------------------------------------------------------------------------------- 1 | part of 'message_bloc.dart'; 2 | 3 | abstract class MessageState extends Equatable { 4 | const MessageState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class MessageInitial extends MessageState {} 11 | 12 | abstract class MessagesState extends MessageState { 13 | final List usersTyping; 14 | 15 | final List messages; 16 | 17 | MessagesState(this.messages, [this.usersTyping = const []]) : super(); 18 | 19 | @override 20 | List get props => [messages, usersTyping]; 21 | } 22 | 23 | class MessagesLoadInProgressState extends MessageState {} 24 | 25 | class MessagesLoadSuccessState extends MessagesState { 26 | MessagesLoadSuccessState(List messages) : super(messages); 27 | } 28 | 29 | class MessagesLoadFailureState extends MessageState {} 30 | 31 | class MessageReceiveState extends MessagesState { 32 | MessageReceiveState(List messages, 33 | [List usersTyping = const []]) 34 | : super(messages, usersTyping); 35 | } 36 | 37 | class MessageDeleteState extends MessagesState { 38 | MessageDeleteState(List messages, 39 | [List usersTyping = const []]) 40 | : super(messages, usersTyping); 41 | } 42 | 43 | class MessageTypingState extends MessagesState { 44 | MessageTypingState(List messages, 45 | [List usersTyping = const []]) 46 | : super(messages, usersTyping); 47 | } 48 | 49 | class PreviousMessagesLoadState extends MessagesState { 50 | final double previousScrollHeight; 51 | 52 | PreviousMessagesLoadState(List messages, this.previousScrollHeight, 53 | [List usersTyping = const []]) 54 | : super(messages, usersTyping); 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/enum/message_type.dart: -------------------------------------------------------------------------------- 1 | enum MessageType { room, direct } 2 | 3 | extension MessageTypeExtension on MessageType { 4 | String get name { 5 | switch (this) { 6 | case MessageType.direct: 7 | return 'direct'; 8 | default: 9 | return 'room'; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/models/message.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/models/user.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class Message extends Equatable { 5 | late final String id; 6 | late final String message; 7 | late final String? to; 8 | late final String? room; 9 | late final dynamic from; 10 | late final String createdAt; 11 | late final DateTime createdAtDate; 12 | 13 | Message({ 14 | required this.id, 15 | required this.message, 16 | this.room, 17 | this.to, 18 | required this.from, 19 | required this.createdAt, 20 | }) : super(); 21 | 22 | Message.fromJson(Map json) { 23 | id = json['_id']; 24 | message = json['message']; 25 | to = json['to']; 26 | room = json['room']; 27 | from = json['from'] is Map ? User.fromJson(json['from']) : json['from']; 28 | createdAt = json['createdAt']; 29 | createdAtDate = DateTime.parse(createdAt); 30 | } 31 | 32 | static List fromList(List list) { 33 | return list.map((e) => Message.fromJson(e)).toList(); 34 | } 35 | 36 | @override 37 | List get props => [id, message, to, from, createdAt]; 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/models/typing.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/models/user.dart'; 2 | import 'package:auth/src/features/room/logic/models/room.dart'; 3 | import 'package:equatable/equatable.dart'; 4 | 5 | class Typing extends Equatable { 6 | late final Room? room; 7 | late final User user; 8 | 9 | Typing({this.room, required this.user}) : super(); 10 | 11 | Typing.fromJson(Map json) { 12 | room = json['room'] != null ? Room.fromJson(json['room']) : null; 13 | user = User.fromJson(json['user']); 14 | } 15 | 16 | @override 17 | List get props => [room, user]; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/providers/message_api_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/interceptors/auth_token_interceptor.dart'; 2 | import 'package:auth/src/features/messages/logic/enum/message_type.dart'; 3 | import 'package:auth/src/features/messages/logic/models/message.dart'; 4 | import 'package:auth/src/shared/logic/http/api.dart'; 5 | 6 | class MessageAPIProvider { 7 | Future> getMessages( 8 | MessageType type, 9 | String id, 10 | int limit, 11 | String? before, 12 | ) async { 13 | final params = {'limit': limit, 'before': before}; 14 | 15 | for (String key in [...params.keys]) { 16 | if (params[key] == null) { 17 | params.remove(key); 18 | } 19 | } 20 | 21 | final response = await api.get( 22 | '/message/${type.name}/$id', 23 | queryParameters: params, 24 | ); 25 | 26 | return Message.fromList(response.data); 27 | } 28 | 29 | Future getFirstMessage(MessageType type, String id) async { 30 | final response = await api.get('/message/${type.name}-first-message/$id'); 31 | 32 | if (response.data is String && (response.data as String).trim().isEmpty) { 33 | return null; 34 | } 35 | 36 | return Message.fromJson(response.data); 37 | } 38 | 39 | Future deleteMessage(MessageType type, Message message) async { 40 | final response = await api.delete( 41 | '/message/${type.name}', 42 | data: { 43 | 'messageId': message.id, 44 | 'roomId': message.room, 45 | 'to': message.to, 46 | }, 47 | options: Options(headers: { 48 | AuthTokenInterceptor.skipHeader: true, 49 | }), 50 | ); 51 | 52 | return Message.fromJson(response.data); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/features/messages/logic/repository/message_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/messages/logic/enum/message_type.dart'; 2 | import 'package:auth/src/features/messages/logic/models/message.dart'; 3 | import 'package:auth/src/features/messages/logic/providers/message_api_provider.dart'; 4 | 5 | class MessageRepository { 6 | final provider = MessageAPIProvider(); 7 | 8 | Future> getMessages({ 9 | required MessageType type, 10 | required String id, 11 | required int limit, 12 | String? before, 13 | }) { 14 | return provider.getMessages(type, id, limit, before); 15 | } 16 | 17 | Future getFirstMessage(MessageType type, String id) { 18 | return provider.getFirstMessage(type, id); 19 | } 20 | 21 | Future deleteMessage(MessageType type, Message message) { 22 | return provider.deleteMessage(type, message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/features/messages/views/screens/direct_message_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/messages/logic/bloc/direct_message_bloc.dart'; 2 | import 'package:auth/src/features/messages/logic/bloc/message_bloc.dart'; 3 | import 'package:auth/src/features/messages/logic/enum/message_type.dart'; 4 | import 'package:auth/src/features/messages/views/widgets/messages.dart'; 5 | import 'package:auth/src/shared/views/widgets/user_status.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/widgets.dart'; 8 | import 'package:flutter_bloc/flutter_bloc.dart'; 9 | 10 | class DirectMessageArguments { 11 | final String username; 12 | final bool fromMessages; 13 | 14 | DirectMessageArguments({ 15 | required this.username, 16 | this.fromMessages = false, 17 | }); 18 | } 19 | 20 | class DirectMessageScreen extends StatefulWidget { 21 | static const routeName = '/direct-message'; 22 | 23 | static Route route(RouteSettings settings) { 24 | final args = settings.arguments as DirectMessageArguments; 25 | 26 | return MaterialPageRoute( 27 | builder: (_) => BlocProvider( 28 | create: (_) => DirectMessageBloc( 29 | args.username, 30 | fromMessages: args.fromMessages, 31 | ), 32 | child: DirectMessageScreen(fromMessages: args.fromMessages), 33 | ), 34 | ); 35 | } 36 | 37 | final bool fromMessages; 38 | 39 | DirectMessageScreen({Key? key, required this.fromMessages}) : super(key: key); 40 | 41 | @override 42 | _DirectMessageScreenState createState() => _DirectMessageScreenState(); 43 | } 44 | 45 | class _DirectMessageScreenState extends State { 46 | @override 47 | void initState() { 48 | super.initState(); 49 | 50 | context.read().add(UserLoadedEvent()); 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return BlocConsumer( 56 | listenWhen: (_, curr) => curr is UserLoadFailureState, 57 | listener: (state, _) => Navigator.pop(context), 58 | builder: (context, state) { 59 | if (state is SocketConnectState) { 60 | return Scaffold( 61 | appBar: AppBar( 62 | title: Row( 63 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 64 | children: [ 65 | Text(state.user.username), 66 | UserStatus( 67 | online: state.user.online, 68 | ), 69 | ], 70 | ), 71 | ), 72 | body: BlocProvider( 73 | create: (context) => MessageBloc( 74 | partnerId: state.user.id, 75 | type: MessageType.direct, 76 | context: context, 77 | fromMessages: widget.fromMessages, 78 | ), 79 | child: Builder( 80 | builder: (context) => Messages( 81 | type: MessageType.direct, 82 | bloc: context.read(), 83 | to: state.user, 84 | ), 85 | ), 86 | ), 87 | ); 88 | } 89 | 90 | return Scaffold( 91 | body: Center( 92 | child: CircularProgressIndicator(), 93 | ), 94 | ); 95 | }, 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/features/notification/logic/enums/notification_type.dart: -------------------------------------------------------------------------------- 1 | enum NotificationType { direct, room } 2 | 3 | extension NotificationTypeExtension on NotificationType { 4 | String get name { 5 | switch (this) { 6 | case NotificationType.direct: 7 | return 'direct'; 8 | default: 9 | return 'room'; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/features/notification/logic/provider/subscription_api_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/interceptors/auth_token_interceptor.dart'; 2 | import 'package:auth/src/shared/logic/http/api.dart'; 3 | import 'package:auth/src/shared/logic/http/interceptors/error_dialog_interceptor.dart'; 4 | 5 | class SubscriptionAPIProvider { 6 | Future registerSubscription(String token) async { 7 | await api.post('/subscription/mobile', data: {'subscription': token}); 8 | } 9 | 10 | Future deleteSubscription(String token) async { 11 | await api.delete( 12 | '/subscription/mobile', 13 | data: {'subscription': token}, 14 | options: Options(headers: { 15 | AuthTokenInterceptor.skipHeader: true, 16 | ErrorDialogInterceptor.skipHeader: true, 17 | }), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/features/notification/logic/repository/notification_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/app.dart'; 2 | import 'package:auth/src/core/socket.dart'; 3 | import 'package:auth/src/features/messages/views/screens/direct_message_screen.dart'; 4 | import 'package:auth/src/features/messages/views/widgets/messages.dart'; 5 | import 'package:auth/src/features/notification/logic/enums/notification_type.dart'; 6 | import 'package:auth/src/features/notification/logic/repository/subscription_repository.dart'; 7 | import 'package:auth/src/features/room/views/screens/room_screen.dart'; 8 | import 'package:firebase_core/firebase_core.dart'; 9 | import 'package:firebase_messaging/firebase_messaging.dart'; 10 | import 'package:flutter/material.dart'; 11 | 12 | class NotificationRepository { 13 | FirebaseMessaging get _fcm => FirebaseMessaging.instance; 14 | 15 | final subscriptionRepository = SubscriptionRepository(); 16 | 17 | Future setup() async { 18 | await Firebase.initializeApp(); 19 | 20 | _fcm.setForegroundNotificationPresentationOptions( 21 | alert: false, 22 | badge: false, 23 | sound: false, 24 | ); 25 | } 26 | 27 | Future init() async { 28 | RemoteMessage? initialMessage = 29 | await FirebaseMessaging.instance.getInitialMessage(); 30 | 31 | if (initialMessage != null) { 32 | _handleBackgroundMessage(initialMessage); 33 | } 34 | 35 | FirebaseMessaging.onMessageOpenedApp.listen( 36 | (message) => _handleBackgroundMessage(message), 37 | ); 38 | 39 | FirebaseMessaging.onMessage.listen( 40 | (message) => _handleForegroundMessage(message), 41 | ); 42 | } 43 | 44 | void _handleForegroundMessage(RemoteMessage message) { 45 | final context = applicationKey.currentContext; 46 | 47 | if (context == null) { 48 | return; 49 | } 50 | 51 | final type = message.data['type']; 52 | 53 | SnackBar? snackBar; 54 | 55 | if (type == NotificationType.room.name && 56 | !_isCurrentPartner(message.data['roomId'])) { 57 | snackBar = SnackBar( 58 | content: Text( 59 | 'Message received from Room: ${message.data['roomTitle']}', 60 | ), 61 | action: SnackBarAction( 62 | label: 'View', 63 | onPressed: () => _redirectToRoom( 64 | context, 65 | message.data['roomId'], 66 | ), 67 | ), 68 | ); 69 | } 70 | 71 | if (type == NotificationType.direct.name && 72 | !_isCurrentPartner(message.data['username'])) { 73 | snackBar = SnackBar( 74 | content: Text( 75 | 'Message received from User: ${message.data['username']}', 76 | ), 77 | action: SnackBarAction( 78 | label: 'View', 79 | onPressed: () => _redirectToUser( 80 | context, 81 | message.data['username'], 82 | ), 83 | ), 84 | ); 85 | } 86 | 87 | if (snackBar == null) { 88 | return; 89 | } 90 | 91 | scaffoldMessengerKey.currentState?.showSnackBar(snackBar); 92 | } 93 | 94 | void _handleBackgroundMessage(RemoteMessage message) { 95 | final context = applicationKey.currentContext; 96 | 97 | if (context == null) { 98 | return; 99 | } 100 | 101 | final type = message.data['type']; 102 | 103 | if (type == NotificationType.room.name) { 104 | _redirectToRoom(context, message.data['roomId']); 105 | } 106 | 107 | if (type == NotificationType.direct.name) { 108 | _redirectToUser(context, message.data['username']); 109 | } 110 | } 111 | 112 | _redirectToUser(BuildContext context, String username) { 113 | if (_isCurrentPartner(username)) { 114 | return; 115 | } 116 | 117 | Navigator.pushNamed( 118 | context, 119 | DirectMessageScreen.routeName, 120 | arguments: DirectMessageArguments( 121 | username: username, 122 | fromMessages: socketManager.socket.connected, 123 | ), 124 | ); 125 | } 126 | 127 | _redirectToRoom(BuildContext context, String roomId) { 128 | if (_isCurrentPartner(roomId)) { 129 | return; 130 | } 131 | 132 | Navigator.pushNamed( 133 | context, 134 | RoomScreen.routeName, 135 | arguments: RoomArguments( 136 | roomId: roomId, 137 | fromMessages: socketManager.socket.connected, 138 | ), 139 | ); 140 | } 141 | 142 | bool _isCurrentPartner(String partnerId) { 143 | return Messages.partnersHistory.length > 0 && 144 | Messages.partnersHistory.last == partnerId; 145 | } 146 | 147 | Future requestPermission() async { 148 | await _fcm.requestPermission(); 149 | 150 | final token = await _fcm.getToken(); 151 | 152 | if (token == null) { 153 | return; 154 | } 155 | 156 | await subscriptionRepository.registerSubscription(token); 157 | } 158 | 159 | Future deleteSubscription() async { 160 | final token = await _fcm.getToken(); 161 | 162 | if (token == null) { 163 | return; 164 | } 165 | 166 | return subscriptionRepository.deleteSubscription(token); 167 | } 168 | } 169 | 170 | final notificationRepository = NotificationRepository(); 171 | -------------------------------------------------------------------------------- /lib/src/features/notification/logic/repository/subscription_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/notification/logic/provider/subscription_api_provider.dart'; 2 | 3 | class SubscriptionRepository { 4 | final _provider = SubscriptionAPIProvider(); 5 | 6 | Future registerSubscription(String token) => 7 | _provider.registerSubscription(token); 8 | 9 | Future deleteSubscription(String token) => 10 | _provider.deleteSubscription(token); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/features/notification/views/widgets/notification_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/cubit/auth_cubit.dart'; 2 | import 'package:auth/src/features/auth/logic/models/user.dart'; 3 | import 'package:auth/src/features/notification/logic/repository/notification_repository.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | class NotificationHandler extends StatefulWidget { 8 | NotificationHandler({Key? key}) : super(key: key); 9 | 10 | @override 11 | _NotificationHandlerState createState() => _NotificationHandlerState(); 12 | } 13 | 14 | class _NotificationHandlerState extends State { 15 | @override 16 | void initState() { 17 | super.initState(); 18 | 19 | if (context.read().state != null) { 20 | notificationRepository.requestPermission(); 21 | } 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return BlocListener( 27 | listenWhen: (prev, curr) => curr != null, 28 | listener: (context, state) => notificationRepository.requestPermission(), 29 | child: Container(), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/bloc/room_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:auth/src/core/socket.dart'; 4 | import 'package:auth/src/features/auth/logic/models/user.dart'; 5 | import 'package:auth/src/features/room/logic/models/room.dart'; 6 | import 'package:auth/src/features/room/logic/repository/room_repository.dart'; 7 | import 'package:bloc/bloc.dart'; 8 | import 'package:equatable/equatable.dart'; 9 | import 'package:socket_io_client/socket_io_client.dart'; 10 | 11 | part 'room_event.dart'; 12 | part 'room_state.dart'; 13 | 14 | class RoomBloc extends Bloc { 15 | final repository = RoomRepository(); 16 | final socket = socketManager.socket; 17 | final bool fromMessages; 18 | 19 | Timer? updateRoomTimer; 20 | String? joinedRoomId; 21 | 22 | RoomBloc(this.fromMessages) : super(RoomInitialState()) { 23 | initEvents(); 24 | } 25 | 26 | void initEvents() { 27 | on(_onRoomChecked); 28 | on(_onRoomJoined); 29 | on(_onRoomUserJoin); 30 | on(_onRoomUserLeave); 31 | on(_onSocketDisconnected); 32 | on(_onRoomConnected); 33 | 34 | on( 35 | (event, emit) => emit.call(DirectRooomDeleteState()), 36 | ); 37 | 38 | on( 39 | (event, emit) => emit.call(RoomJoinSuccessState(event.room)), 40 | ); 41 | } 42 | 43 | void initTimers() { 44 | updateRoomTimer = Timer.periodic( 45 | Duration(seconds: 5), 46 | (_) => add(RoomCheckedEvent(joinedRoomId as String)), 47 | ); 48 | } 49 | 50 | void initSocket() { 51 | socket.onDisconnect((data) => add(SocketDisconnectedEvent())); 52 | 53 | socket.onConnect((data) => add(SocketConnectedEvent())); 54 | 55 | socket.on( 56 | 'room:join', 57 | (data) => add(RoomUserJoinEvent(User.fromJson(data))), 58 | ); 59 | 60 | socket.on( 61 | 'room:leave', 62 | (data) => add(RoomUserLeaveEvent(User.fromJson(data))), 63 | ); 64 | 65 | socket.on( 66 | 'room:update', 67 | (data) => add(DirectRoomUpdatedEvent(Room.fromJson(data))), 68 | ); 69 | 70 | socket.on('room:delete', (data) => add(DirectRoomDeletedEvent())); 71 | 72 | if (!socket.connected) { 73 | socketManager.init( 74 | () => socket.emit('room:subscribe', joinedRoomId), 75 | ); 76 | } 77 | } 78 | 79 | @override 80 | Future close() { 81 | if (!fromMessages) { 82 | socketManager.dispose(); 83 | } 84 | 85 | updateRoomTimer?.cancel(); 86 | 87 | return super.close(); 88 | } 89 | 90 | FutureOr _onRoomChecked( 91 | RoomCheckedEvent event, 92 | Emitter emit, 93 | ) async { 94 | if (state is RoomCheckInProgressState) { 95 | return; 96 | } 97 | 98 | if (state is RoomInitialState || joinedRoomId == null) { 99 | emit.call(RoomCheckInProgressState()); 100 | } 101 | 102 | try { 103 | final room = await repository.getRoom(event.roomId); 104 | 105 | if (state is SocketConnectState) { 106 | emit.call(SocketConnectState(room)); 107 | } else { 108 | emit.call(RoomCheckSuccessState(room, isDialog: event.isDialog)); 109 | } 110 | } catch (e) { 111 | emit.call(RoomCheckFailureState()); 112 | } 113 | } 114 | 115 | FutureOr _onRoomJoined( 116 | RoomJoinedEvent event, 117 | Emitter emit, 118 | ) async { 119 | emit.call(RoomJoinInProgressState()); 120 | 121 | try { 122 | final room = await repository.joinRoom(event.roomId); 123 | 124 | joinedRoomId = room.id; 125 | 126 | emit.call(RoomJoinSuccessState(room)); 127 | 128 | initSocket(); 129 | 130 | initTimers(); 131 | } catch (_) { 132 | emit.call(RoomJoinFailureState()); 133 | } 134 | } 135 | 136 | FutureOr _onRoomUserJoin( 137 | RoomUserJoinEvent event, 138 | Emitter emit, 139 | ) { 140 | if (!(state is RoomParamState)) { 141 | return null; 142 | } 143 | 144 | final data = state as RoomParamState; 145 | 146 | emit.call(RoomJoinSuccessState( 147 | data.room.copyWith(members: [...data.room.members, event.user]), 148 | )); 149 | } 150 | 151 | FutureOr _onRoomUserLeave( 152 | RoomUserLeaveEvent event, 153 | Emitter emit, 154 | ) { 155 | if (!(state is RoomParamState)) { 156 | return null; 157 | } 158 | 159 | final data = state as RoomParamState; 160 | 161 | emit.call( 162 | RoomJoinSuccessState(data.room.copyWith( 163 | members: data.room.members.where((e) => e.id != event.user.id).toList(), 164 | )), 165 | ); 166 | } 167 | 168 | FutureOr _onRoomConnected( 169 | SocketConnectedEvent event, 170 | Emitter emit, 171 | ) { 172 | if (!(state is RoomParamState)) { 173 | return null; 174 | } 175 | 176 | final data = state as RoomParamState; 177 | 178 | emit.call(SocketConnectState(data.room)); 179 | } 180 | 181 | FutureOr _onSocketDisconnected( 182 | SocketDisconnectedEvent event, 183 | Emitter emit, 184 | ) { 185 | if (!(state is RoomParamState)) { 186 | return null; 187 | } 188 | 189 | final data = state as RoomParamState; 190 | 191 | emit.call(SocketDisconnectState(data.room)); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/bloc/room_event.dart: -------------------------------------------------------------------------------- 1 | part of 'room_bloc.dart'; 2 | 3 | abstract class RoomEvent extends Equatable { 4 | const RoomEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class _RoomParamsEvent extends RoomEvent { 11 | final String roomId; 12 | 13 | _RoomParamsEvent(this.roomId); 14 | 15 | @override 16 | List get props => [roomId]; 17 | } 18 | 19 | class RoomCheckedEvent extends _RoomParamsEvent { 20 | final bool isDialog; 21 | 22 | RoomCheckedEvent(String roomId, {this.isDialog = false}) : super(roomId); 23 | 24 | @override 25 | List get props => [...super.props, isDialog]; 26 | } 27 | 28 | class RoomJoinedEvent extends _RoomParamsEvent { 29 | RoomJoinedEvent(String roomId) : super(roomId); 30 | } 31 | 32 | class _RoomObjectParamEvent extends RoomEvent { 33 | final Room room; 34 | 35 | _RoomObjectParamEvent(this.room); 36 | 37 | @override 38 | List get props => [room]; 39 | } 40 | 41 | class SocketDisconnectedEvent extends RoomEvent {} 42 | 43 | class SocketConnectedEvent extends RoomEvent {} 44 | 45 | class DirectRoomDeletedEvent extends RoomEvent {} 46 | 47 | class _UserEvent extends RoomEvent { 48 | final User user; 49 | 50 | _UserEvent(this.user) : super(); 51 | 52 | @override 53 | List get props => [user]; 54 | } 55 | 56 | class RoomUserJoinEvent extends _UserEvent { 57 | RoomUserJoinEvent(User user) : super(user); 58 | } 59 | 60 | class RoomUserLeaveEvent extends _UserEvent { 61 | RoomUserLeaveEvent(User user) : super(user); 62 | } 63 | 64 | class DirectRoomUpdatedEvent extends _RoomObjectParamEvent { 65 | DirectRoomUpdatedEvent(Room room) : super(room); 66 | } 67 | 68 | class UpdateRoomInfoEvent extends RoomEvent {} 69 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/bloc/room_state.dart: -------------------------------------------------------------------------------- 1 | part of 'room_bloc.dart'; 2 | 3 | abstract class RoomState extends Equatable { 4 | const RoomState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class RoomInitialState extends RoomState {} 11 | 12 | class RoomCheckInProgressState extends RoomState {} 13 | 14 | class RoomCheckFailureState extends RoomState {} 15 | 16 | class RoomParamState extends RoomState { 17 | final Room room; 18 | 19 | RoomParamState(this.room) : super(); 20 | 21 | @override 22 | List get props => [room]; 23 | } 24 | 25 | class RoomCheckSuccessState extends RoomParamState { 26 | final bool isDialog; 27 | 28 | RoomCheckSuccessState(Room room, {this.isDialog = false}) : super(room); 29 | 30 | @override 31 | List get props => [...super.props, isDialog]; 32 | } 33 | 34 | class RoomJoinInProgressState extends RoomState {} 35 | 36 | class RoomJoinFailureState extends RoomState {} 37 | 38 | class RoomJoinSuccessState extends RoomParamState { 39 | RoomJoinSuccessState(Room room) : super(room); 40 | } 41 | 42 | class DirectRooomDeleteState extends RoomState {} 43 | 44 | class SocketConnectState extends RoomParamState { 45 | SocketConnectState(Room room) : super(room); 46 | } 47 | 48 | class SocketDisconnectState extends RoomParamState { 49 | SocketDisconnectState(Room room) : super(room); 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/bloc/rooms_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:auth/src/features/room/logic/models/room.dart'; 4 | import 'package:auth/src/features/room/logic/repository/room_repository.dart'; 5 | import 'package:bloc/bloc.dart'; 6 | import 'package:equatable/equatable.dart'; 7 | 8 | part 'rooms_event.dart'; 9 | part 'rooms_state.dart'; 10 | 11 | class RoomsBloc extends Bloc { 12 | RoomRepository repository; 13 | 14 | RoomsBloc({required this.repository}) : super(RoomsLoadInProgressState()) { 15 | on(_onRoomsLoaded); 16 | on(_onRoomCreated); 17 | on(_onRoomUpdated); 18 | on(_onRoomDeleted); 19 | on(_onRoomLeft); 20 | } 21 | 22 | FutureOr _onRoomsLoaded( 23 | RoomsLoadedEvent event, 24 | Emitter emit, 25 | ) async { 26 | emit.call(RoomsLoadInProgressState()); 27 | 28 | try { 29 | final userRooms = await repository.getUserRooms(); 30 | final memberRooms = await repository.getRoomsByMember(); 31 | final publicRooms = await repository.getPublicRooms(); 32 | 33 | emit.call(RoomsLoadSuccessState( 34 | userRooms: userRooms, 35 | memberRooms: memberRooms, 36 | publicRooms: publicRooms, 37 | )); 38 | } catch (e) { 39 | emit.call(RoomsLoadFailureState()); 40 | } 41 | } 42 | 43 | FutureOr _onRoomCreated( 44 | RoomCreatedEvent event, 45 | Emitter emit, 46 | ) async { 47 | final data = state as RoomsLoadSuccessState; 48 | 49 | final room = await repository.createRoom( 50 | title: event.title, 51 | isPublic: event.isPublic, 52 | ); 53 | 54 | List publicRooms = List.from(data.publicRooms); 55 | 56 | if (event.isPublic) { 57 | publicRooms.add(room); 58 | } 59 | 60 | emit.call(RoomsLoadSuccessState( 61 | memberRooms: data.memberRooms, 62 | publicRooms: publicRooms, 63 | userRooms: List.from(data.userRooms)..add(room), 64 | )); 65 | } 66 | 67 | FutureOr _onRoomUpdated( 68 | RoomUpdatedEvent event, 69 | Emitter emit, 70 | ) async { 71 | final data = state as RoomsLoadSuccessState; 72 | 73 | final response = await repository.updateRoom( 74 | event.id, 75 | title: event.title, 76 | isPublic: event.isPublic, 77 | ); 78 | 79 | final room = Room( 80 | id: event.id, 81 | title: event.title, 82 | isPublic: event.isPublic, 83 | members: response.members, 84 | owner: response.owner, 85 | ); 86 | 87 | if (room.isPublic && !data.publicRooms.any((e) => e.id == room.id)) { 88 | data.publicRooms.add(room); 89 | } 90 | 91 | final replaceHandler = (Room e) => event.id == e.id ? room : e; 92 | 93 | emit.call(RoomsLoadSuccessState( 94 | memberRooms: data.memberRooms.map(replaceHandler).toList(), 95 | publicRooms: data.publicRooms 96 | .map(replaceHandler) 97 | .where((e) => e.isPublic) 98 | .toList(), 99 | userRooms: data.userRooms.map(replaceHandler).toList(), 100 | )); 101 | } 102 | 103 | FutureOr _onRoomDeleted( 104 | RoomDeletedEvent event, 105 | Emitter emit, 106 | ) async { 107 | final data = state as RoomsLoadSuccessState; 108 | 109 | await repository.deleteRoom(event.id); 110 | 111 | final deleteHandler = (Room e) => e.id != event.id; 112 | 113 | emit.call(RoomsLoadSuccessState( 114 | memberRooms: data.memberRooms.where(deleteHandler).toList(), 115 | publicRooms: data.publicRooms.where(deleteHandler).toList(), 116 | userRooms: data.userRooms.where(deleteHandler).toList(), 117 | )); 118 | } 119 | 120 | FutureOr _onRoomLeft( 121 | RoomLeftEvent event, 122 | Emitter emit, 123 | ) async { 124 | final data = state as RoomsLoadSuccessState; 125 | 126 | await repository.leaveRoom(event.id); 127 | 128 | emit.call(RoomsLoadSuccessState( 129 | memberRooms: 130 | data.memberRooms.where((room) => room.id != event.id).toList(), 131 | publicRooms: data.publicRooms, 132 | userRooms: data.userRooms, 133 | )); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/bloc/rooms_event.dart: -------------------------------------------------------------------------------- 1 | part of 'rooms_bloc.dart'; 2 | 3 | abstract class RoomsEvent extends Equatable { 4 | const RoomsEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class RoomsLoadedEvent extends RoomsEvent {} 11 | 12 | class _RoomParamsEvent extends RoomsEvent { 13 | final String title; 14 | final bool isPublic; 15 | 16 | _RoomParamsEvent({required this.title, required this.isPublic}); 17 | 18 | @override 19 | List get props => [title, isPublic]; 20 | } 21 | 22 | class RoomCreatedEvent extends _RoomParamsEvent { 23 | RoomCreatedEvent({required String title, required bool isPublic}) 24 | : super(title: title, isPublic: isPublic); 25 | } 26 | 27 | class RoomUpdatedEvent extends _RoomParamsEvent { 28 | final String id; 29 | 30 | RoomUpdatedEvent({ 31 | required this.id, 32 | required String title, 33 | required bool isPublic, 34 | }) : super(title: title, isPublic: isPublic); 35 | 36 | @override 37 | List get props => [id, ...super.props]; 38 | } 39 | 40 | class RoomDeletedEvent extends RoomsEvent { 41 | final String id; 42 | 43 | RoomDeletedEvent(this.id); 44 | 45 | @override 46 | List get props => [id]; 47 | } 48 | 49 | class RoomLeftEvent extends RoomsEvent { 50 | final String id; 51 | 52 | RoomLeftEvent(this.id); 53 | 54 | @override 55 | List get props => [id]; 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/bloc/rooms_state.dart: -------------------------------------------------------------------------------- 1 | part of 'rooms_bloc.dart'; 2 | 3 | abstract class RoomsState extends Equatable { 4 | const RoomsState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class RoomsLoadInProgressState extends RoomsState {} 11 | 12 | class RoomsLoadFailureState extends RoomsState {} 13 | 14 | class RoomsLoadSuccessState extends RoomsState { 15 | final List userRooms; 16 | final List publicRooms; 17 | final List memberRooms; 18 | 19 | RoomsLoadSuccessState({ 20 | this.userRooms = const [], 21 | this.publicRooms = const [], 22 | this.memberRooms = const [], 23 | }); 24 | 25 | @override 26 | List get props => [userRooms, publicRooms, memberRooms]; 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/models/room.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/models/user.dart'; 2 | 3 | class Room { 4 | late final String id; 5 | late final String title; 6 | late final bool isPublic; 7 | late final List members; 8 | late final dynamic owner; 9 | 10 | Room({ 11 | required this.id, 12 | required this.title, 13 | required this.isPublic, 14 | required this.members, 15 | required this.owner, 16 | }); 17 | 18 | Room copyWith({ 19 | String? id, 20 | String? title, 21 | bool? isPublic, 22 | List? members, 23 | dynamic owner, 24 | }) { 25 | return Room( 26 | id: id ?? this.id, 27 | title: title ?? this.title, 28 | isPublic: isPublic ?? this.isPublic, 29 | members: members ?? this.members, 30 | owner: owner ?? this.owner, 31 | ); 32 | } 33 | 34 | Room.fromJson(Map json) { 35 | id = json['_id']; 36 | title = json['title']; 37 | isPublic = json['isPublic']; 38 | 39 | if (json['members'].length > 0) { 40 | if (json['members'][0] is String) { 41 | members = json['members']; 42 | } else { 43 | members = User.fromList(json['members']); 44 | 45 | members.sort((a, b) { 46 | return a.compareTo(b); 47 | }); 48 | } 49 | } else { 50 | members = []; 51 | } 52 | 53 | owner = 54 | json['owner'] is String ? json['owner'] : User.fromJson(json['owner']); 55 | } 56 | 57 | static List fromList(List list) { 58 | return list.map((e) => Room.fromJson(e)).toList(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/provider/room_api_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/room/logic/models/room.dart'; 2 | import 'package:auth/src/shared/logic/http/api.dart'; 3 | 4 | class RoomAPIProvider { 5 | Future getRoom(String roomId) async { 6 | final response = await api.get('/room/id/$roomId'); 7 | 8 | return Room.fromJson(response.data); 9 | } 10 | 11 | Future> getPublicRooms() async { 12 | final response = await api.get('/room/public'); 13 | 14 | return Room.fromList(response.data); 15 | } 16 | 17 | Future> getRoomsByMember() async { 18 | final response = await api.get('/room/member'); 19 | 20 | return Room.fromList(response.data); 21 | } 22 | 23 | Future> getUserRooms() async { 24 | final response = await api.get('/room'); 25 | 26 | return Room.fromList(response.data); 27 | } 28 | 29 | Future createRoom({ 30 | required String title, 31 | required bool isPublic, 32 | }) async { 33 | final response = await api.post( 34 | '/room', 35 | data: { 36 | 'title': title, 37 | 'isPublic': isPublic, 38 | }, 39 | ); 40 | 41 | return Room.fromJson(response.data); 42 | } 43 | 44 | deleteRoom(String roomId) { 45 | return api.delete('/room/delete/$roomId'); 46 | } 47 | 48 | Future updateRoom( 49 | String roomId, { 50 | required String title, 51 | required bool isPublic, 52 | }) async { 53 | final response = await api.put( 54 | '/room/$roomId', 55 | data: { 56 | 'title': title, 57 | 'isPublic': isPublic, 58 | }, 59 | ); 60 | 61 | return Room.fromJson(response.data); 62 | } 63 | 64 | Future joinRoom(String roomId) async { 65 | final response = await api.post( 66 | '/room/join', 67 | data: { 68 | 'roomId': roomId, 69 | }, 70 | ); 71 | 72 | return Room.fromJson(response.data); 73 | } 74 | 75 | Future leaveRoom(String roomId) async { 76 | final response = await api.delete('/room/leave/$roomId'); 77 | 78 | return Room.fromJson(response.data); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/features/room/logic/repository/room_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/room/logic/models/room.dart'; 2 | import 'package:auth/src/features/room/logic/provider/room_api_provider.dart'; 3 | 4 | class RoomRepository { 5 | final _provider = RoomAPIProvider(); 6 | 7 | Future getRoom(String roomId) async { 8 | return _provider.getRoom(roomId); 9 | } 10 | 11 | Future> getPublicRooms() async { 12 | return _provider.getPublicRooms(); 13 | } 14 | 15 | Future> getRoomsByMember() async { 16 | return _provider.getRoomsByMember(); 17 | } 18 | 19 | Future> getUserRooms() async { 20 | return _provider.getUserRooms(); 21 | } 22 | 23 | Future createRoom({ 24 | required String title, 25 | required bool isPublic, 26 | }) async { 27 | return _provider.createRoom( 28 | title: title, 29 | isPublic: isPublic, 30 | ); 31 | } 32 | 33 | deleteRoom(String roomId) { 34 | return _provider.deleteRoom(roomId); 35 | } 36 | 37 | Future updateRoom( 38 | String roomId, { 39 | required String title, 40 | required bool isPublic, 41 | }) async { 42 | return _provider.updateRoom( 43 | roomId, 44 | title: title, 45 | isPublic: isPublic, 46 | ); 47 | } 48 | 49 | Future joinRoom(String roomId) async { 50 | return _provider.joinRoom(roomId); 51 | } 52 | 53 | Future leaveRoom(String roomId) async { 54 | return _provider.leaveRoom(roomId); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/features/room/views/screens/room_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/models/user.dart'; 2 | import 'package:auth/src/features/messages/logic/bloc/message_bloc.dart'; 3 | import 'package:auth/src/features/messages/logic/enum/message_type.dart'; 4 | import 'package:auth/src/features/messages/views/screens/direct_message_screen.dart'; 5 | import 'package:auth/src/features/messages/views/widgets/messages.dart'; 6 | import 'package:auth/src/features/room/logic/bloc/room_bloc.dart'; 7 | import 'package:auth/src/shared/views/widgets/user_status.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter/widgets.dart'; 10 | import 'package:flutter_bloc/flutter_bloc.dart'; 11 | 12 | class RoomArguments { 13 | final String roomId; 14 | final bool fromMessages; 15 | 16 | RoomArguments({ 17 | required this.roomId, 18 | this.fromMessages = false, 19 | }); 20 | } 21 | 22 | class RoomScreen extends StatefulWidget { 23 | static const routeName = '/room'; 24 | 25 | static route(RouteSettings settings) { 26 | final args = settings.arguments as RoomArguments; 27 | 28 | return MaterialPageRoute(builder: (_) { 29 | return MultiBlocProvider( 30 | providers: [ 31 | BlocProvider(create: (_) => RoomBloc(args.fromMessages)), 32 | BlocProvider( 33 | create: (context) => MessageBloc( 34 | partnerId: args.roomId, 35 | type: MessageType.room, 36 | context: context, 37 | fromMessages: args.fromMessages, 38 | ), 39 | ), 40 | ], 41 | child: RoomScreen(roomId: args.roomId), 42 | ); 43 | }); 44 | } 45 | 46 | final String roomId; 47 | 48 | const RoomScreen({Key? key, required this.roomId}) : super(key: key); 49 | 50 | @override 51 | _RoomScreenState createState() => _RoomScreenState(); 52 | } 53 | 54 | class _RoomScreenState extends State { 55 | bool _showMembers = false; 56 | 57 | @override 58 | void initState() { 59 | super.initState(); 60 | 61 | final bloc = context.read(); 62 | 63 | bloc.add(RoomJoinedEvent(widget.roomId)); 64 | } 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | return BlocConsumer( 69 | listener: (_, state) => Navigator.pop(context), 70 | listenWhen: (_, curr) => 71 | curr is RoomJoinFailureState || 72 | curr is RoomCheckFailureState || 73 | curr is DirectRooomDeleteState, 74 | builder: (_, state) { 75 | if (state is SocketConnectState) { 76 | return Scaffold( 77 | appBar: AppBar( 78 | title: Text(state.room.title), 79 | actions: [ 80 | IconButton( 81 | onPressed: () => setState(() { 82 | _showMembers = !_showMembers; 83 | }), 84 | icon: Icon(Icons.menu), 85 | ), 86 | ], 87 | ), 88 | body: Stack( 89 | children: [ 90 | Messages( 91 | type: MessageType.room, 92 | room: state.room, 93 | bloc: context.read(), 94 | ), 95 | if (_showMembers) _RoomMembers(members: state.room.members) 96 | ], 97 | ), 98 | ); 99 | } 100 | 101 | return Scaffold( 102 | body: Center( 103 | child: CircularProgressIndicator(), 104 | ), 105 | ); 106 | }, 107 | ); 108 | } 109 | } 110 | 111 | class _RoomMembers extends StatefulWidget { 112 | final List members; 113 | 114 | _RoomMembers({Key? key, required this.members}) : super(key: key); 115 | 116 | @override 117 | _RoomMembersState createState() => _RoomMembersState(); 118 | } 119 | 120 | class _RoomMembersState extends State<_RoomMembers> { 121 | bool _showOnlineUsers = false; 122 | 123 | @override 124 | Widget build(BuildContext context) { 125 | return Container( 126 | color: Colors.white, 127 | child: Column( 128 | children: [ 129 | SwitchListTile( 130 | title: Text(_showOnlineUsers ? 'Online Members' : 'All Members'), 131 | value: _showOnlineUsers, 132 | onChanged: (value) => 133 | setState(() => _showOnlineUsers = !_showOnlineUsers), 134 | ), 135 | Expanded( 136 | child: ListView.builder( 137 | itemBuilder: (_, index) { 138 | final member = widget.members[index] as User; 139 | 140 | if (_showOnlineUsers && !member.online) { 141 | return Container(); 142 | } 143 | 144 | return MaterialButton( 145 | padding: EdgeInsets.all(0), 146 | onPressed: () => Navigator.pushNamed( 147 | context, 148 | DirectMessageScreen.routeName, 149 | arguments: DirectMessageArguments( 150 | username: member.username, 151 | fromMessages: true, 152 | ), 153 | ), 154 | child: ListTile( 155 | title: Text(member.username), 156 | trailing: UserStatus(online: member.online), 157 | ), 158 | ); 159 | }, 160 | itemCount: widget.members.length, 161 | ), 162 | ) 163 | ], 164 | ), 165 | ); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /lib/src/features/room/views/widgets/dialog/join_room_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/room/logic/bloc/room_bloc.dart'; 2 | import 'package:auth/src/features/room/views/screens/room_screen.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | class JoinRoomDialog extends StatefulWidget { 8 | final RoomBloc bloc; 9 | 10 | JoinRoomDialog({ 11 | Key? key, 12 | required this.bloc, 13 | }) : super(key: key); 14 | 15 | @override 16 | _JoinRoomDialogState createState() => _JoinRoomDialogState(); 17 | } 18 | 19 | class _JoinRoomDialogState extends State { 20 | String _roomId = ''; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return AlertDialog( 25 | title: Text('Join Room'), 26 | content: Column( 27 | mainAxisSize: MainAxisSize.min, 28 | children: [ 29 | BlocListener( 30 | listenWhen: (prev, curr) => 31 | prev != curr && curr is RoomCheckSuccessState && curr.isDialog, 32 | listener: (context, state) { 33 | Navigator.pop(context); 34 | 35 | Navigator.pushNamed( 36 | context, 37 | RoomScreen.routeName, 38 | arguments: RoomArguments( 39 | roomId: (state as RoomCheckSuccessState).room.id, 40 | ), 41 | ); 42 | }, 43 | bloc: widget.bloc, 44 | child: Container(), 45 | ), 46 | Form( 47 | child: TextFormField( 48 | initialValue: _roomId, 49 | decoration: InputDecoration(hintText: 'Code'), 50 | onChanged: (value) => setState(() => _roomId = value), 51 | autofocus: true, 52 | ), 53 | ), 54 | ], 55 | ), 56 | actions: [ 57 | TextButton( 58 | onPressed: () => Navigator.pop(context), 59 | child: Text('Cancel'), 60 | ), 61 | TextButton( 62 | onPressed: () => _join(), 63 | child: Text('Save'), 64 | ), 65 | ], 66 | ); 67 | } 68 | 69 | _join() { 70 | final id = _roomId.trim(); 71 | 72 | if (id.isEmpty) { 73 | return; 74 | } 75 | 76 | widget.bloc.add(RoomCheckedEvent(id.split('/').last, isDialog: true)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/features/room/views/widgets/dialog/upsert_room_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/room/logic/bloc/rooms_bloc.dart'; 2 | import 'package:auth/src/features/room/logic/models/room.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | class UpsertRoomDialog extends StatefulWidget { 7 | final Room? room; 8 | final RoomsBloc bloc; 9 | 10 | UpsertRoomDialog({Key? key, this.room, required this.bloc}) : super(key: key); 11 | 12 | @override 13 | _UpsertRoomDialogState createState() => _UpsertRoomDialogState(); 14 | } 15 | 16 | class _UpsertRoomDialogState extends State { 17 | String _title = ''; 18 | bool _isPublic = false; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | 24 | if (widget.room != null) { 25 | _title = widget.room!.title; 26 | _isPublic = widget.room!.isPublic; 27 | } 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return AlertDialog( 33 | title: Text(widget.room == null 34 | ? 'Create Room' 35 | : 'Update Room ${widget.room?.title}'), 36 | content: Form( 37 | child: BlocBuilder( 38 | bloc: widget.bloc, 39 | builder: (context, state) { 40 | return Column( 41 | mainAxisSize: MainAxisSize.min, 42 | children: [ 43 | BlocListener( 44 | bloc: widget.bloc, 45 | listenWhen: (prev, curr) => prev != curr, 46 | listener: (context, state) => Navigator.pop(context), 47 | child: Container(), 48 | ), 49 | TextFormField( 50 | initialValue: _title, 51 | decoration: InputDecoration(hintText: 'Title'), 52 | onChanged: (value) => setState(() => _title = value), 53 | autofocus: true, 54 | ), 55 | SwitchListTile( 56 | title: Text('Public'), 57 | value: _isPublic, 58 | onChanged: (value) => setState(() => _isPublic = value), 59 | ) 60 | ], 61 | ); 62 | }, 63 | ), 64 | ), 65 | actions: [ 66 | TextButton( 67 | onPressed: () => Navigator.pop(context), 68 | child: Text('Cancel'), 69 | ), 70 | TextButton( 71 | onPressed: () => _save(context), 72 | child: Text('Save'), 73 | ), 74 | ], 75 | ); 76 | } 77 | 78 | _save(BuildContext context) { 79 | if (widget.room == null) { 80 | widget.bloc.add(RoomCreatedEvent(title: _title, isPublic: _isPublic)); 81 | 82 | return; 83 | } 84 | 85 | widget.bloc.add(RoomUpdatedEvent( 86 | id: widget.room?.id as String, 87 | title: _title, 88 | isPublic: _isPublic, 89 | )); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/features/room/views/widgets/room_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/constants/environments.dart'; 2 | import 'package:auth/src/features/auth/logic/models/user.dart'; 3 | import 'package:auth/src/features/messages/views/screens/direct_message_screen.dart'; 4 | import 'package:auth/src/features/room/logic/bloc/room_bloc.dart'; 5 | import 'package:auth/src/features/room/logic/bloc/rooms_bloc.dart'; 6 | import 'package:auth/src/features/room/logic/models/room.dart'; 7 | import 'package:auth/src/features/room/views/widgets/dialog/upsert_room_dialog.dart'; 8 | import 'package:auth/src/shared/views/widgets/dialog/confirm_dialog_widget.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter/services.dart'; 11 | import 'package:flutter_bloc/flutter_bloc.dart'; 12 | 13 | class RoomTile extends StatelessWidget { 14 | final Room room; 15 | final User user; 16 | final List memberRooms; 17 | 18 | const RoomTile({ 19 | Key? key, 20 | required this.room, 21 | required this.user, 22 | required this.memberRooms, 23 | }) : super(key: key); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final isOwner = room.owner == user.id || room.owner.id == user.id; 28 | 29 | final isMember = memberRooms.any((e) => e.id == this.room.id); 30 | 31 | Widget ownerWidget = Container(); 32 | 33 | if (!isOwner && room.owner is User) { 34 | ownerWidget = GestureDetector( 35 | onTap: () => Navigator.pushNamed( 36 | context, 37 | DirectMessageScreen.routeName, 38 | arguments: DirectMessageArguments(username: room.owner.username), 39 | ), 40 | child: Text((room.owner as User).username), 41 | ); 42 | } 43 | 44 | return Column( 45 | children: [ 46 | ListTile( 47 | leading: CircleAvatar( 48 | child: Icon(Icons.person), 49 | ), 50 | title: Text('${room.title} (${room.members.length})'), 51 | subtitle: ownerWidget, 52 | trailing: TextButton( 53 | onPressed: () => _join(context), 54 | child: Text('Join'), 55 | ), 56 | ), 57 | Row( 58 | children: [ 59 | TextButton( 60 | onPressed: _copy, 61 | child: Text( 62 | 'Copy', 63 | style: TextStyle( 64 | color: Colors.black, 65 | ), 66 | ), 67 | ), 68 | if (isOwner) 69 | TextButton( 70 | onPressed: () => _showUpdateDialog(context), 71 | child: Text( 72 | 'Edit', 73 | style: TextStyle( 74 | color: Colors.black, 75 | ), 76 | ), 77 | ), 78 | if (isOwner) 79 | TextButton( 80 | onPressed: () => _showConfirmDeleteDialog(context), 81 | child: Text( 82 | 'Delete', 83 | style: TextStyle( 84 | color: Colors.black, 85 | ), 86 | ), 87 | ), 88 | if (isMember) 89 | TextButton( 90 | onPressed: () => _showConfirmLeaveDialog(context), 91 | child: Text( 92 | 'Leave', 93 | style: TextStyle( 94 | color: Colors.red, 95 | ), 96 | ), 97 | ), 98 | ], 99 | ), 100 | ], 101 | ); 102 | } 103 | 104 | _showUpdateDialog(BuildContext context) { 105 | return showDialog( 106 | context: context, 107 | builder: (_) => UpsertRoomDialog( 108 | bloc: context.read(), 109 | room: room, 110 | ), 111 | ); 112 | } 113 | 114 | _showConfirmDeleteDialog(BuildContext context) async { 115 | final response = await showDialog( 116 | context: context, 117 | builder: (_) => ConfirmDialogWidget(), 118 | ); 119 | 120 | if (response != null && response) { 121 | _delete(context); 122 | } 123 | } 124 | 125 | _showConfirmLeaveDialog(BuildContext context) async { 126 | final response = await showDialog( 127 | context: context, 128 | builder: (_) => ConfirmDialogWidget(), 129 | ); 130 | 131 | if (response != null && response) { 132 | _leave(context); 133 | } 134 | } 135 | 136 | _delete(BuildContext context) { 137 | context.read().add(RoomDeletedEvent(room.id)); 138 | } 139 | 140 | _leave(BuildContext context) { 141 | context.read().add(RoomLeftEvent(room.id)); 142 | } 143 | 144 | _copy() { 145 | Clipboard.setData( 146 | ClipboardData(text: '${environments.web}/room/${room.id}'), 147 | ); 148 | } 149 | 150 | _join(BuildContext context) { 151 | context.read().add(RoomCheckedEvent(room.id)); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lib/src/features/settings/logic/provider/settings_api_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/shared/logic/http/api.dart'; 2 | 3 | class SettingsAPIProvider { 4 | Future updateUsername(String username) async { 5 | await api.put('/settings/username', data: {'username': username}); 6 | } 7 | 8 | Future updateEmail(String email) async { 9 | await api.put('/settings/email', data: {'email': email}); 10 | } 11 | 12 | Future updatePassword( 13 | String currentPassword, 14 | String password, 15 | String confirmPassword, 16 | ) async { 17 | await api.put('/settings/password', data: { 18 | 'currentPassword': currentPassword, 19 | 'password': password, 20 | 'confirmPassword': confirmPassword 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/features/settings/logic/settings_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/settings/logic/provider/settings_api_provider.dart'; 2 | 3 | class SettingsRepository { 4 | final _provider = SettingsAPIProvider(); 5 | 6 | Future updateUsername(String username) async { 7 | return _provider.updateUsername(username); 8 | } 9 | 10 | Future updateEmail(String email) async { 11 | return _provider.updateEmail(email); 12 | } 13 | 14 | Future updatePassword( 15 | String currentPassword, 16 | String password, 17 | String confirmPassword, 18 | ) async { 19 | return _provider.updatePassword(currentPassword, password, confirmPassword); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/features/user/logic/provider/user_api_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/models/user.dart'; 2 | import 'package:auth/src/shared/logic/http/api.dart'; 3 | 4 | class UserAPIProvider { 5 | Future getUser(String username) async { 6 | final response = await api.get('/user/$username'); 7 | 8 | return User.fromJson(response.data); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/features/user/logic/repository/user_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/auth/logic/models/user.dart'; 2 | import 'package:auth/src/features/user/logic/provider/user_api_provider.dart'; 3 | 4 | class UserRepository { 5 | final _provider = UserAPIProvider(); 6 | 7 | Future getUser(String username) => _provider.getUser(username); 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/shared/logic/http/api.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/constants/environments.dart'; 2 | import 'package:auth/src/features/auth/logic/interceptors/auth_token_interceptor.dart'; 3 | import 'package:auth/src/shared/logic/http/interceptors/error_dialog_interceptor.dart'; 4 | import 'package:dio/dio.dart'; 5 | 6 | export 'package:dio/dio.dart'; 7 | 8 | Dio _createHttpClient() { 9 | final api = new Dio( 10 | new BaseOptions( 11 | baseUrl: environments.api, 12 | contentType: Headers.jsonContentType, 13 | responseType: ResponseType.json, 14 | ), 15 | ); 16 | 17 | api 18 | ..interceptors.add(new ErrorDialogInterceptor()) 19 | ..interceptors.add(new AuthTokenInterceptor(api)); 20 | 21 | return api; 22 | } 23 | 24 | final api = _createHttpClient(); 25 | -------------------------------------------------------------------------------- /lib/src/shared/logic/http/interceptors/error_dialog_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/app.dart'; 2 | import 'package:auth/src/features/auth/logic/interceptors/auth_token_interceptor.dart'; 3 | import 'package:auth/src/features/auth/logic/repository/auth_repository.dart'; 4 | import 'package:auth/src/shared/views/widgets/dialog/alert_dialog_widget.dart'; 5 | import 'package:dio/dio.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | class ErrorDialogInterceptor extends Interceptor { 10 | static const skipHeader = 'skipDialog'; 11 | 12 | @override 13 | onError(DioError err, ErrorInterceptorHandler handler) async { 14 | final context = applicationKey.currentContext; 15 | 16 | if (context == null) { 17 | return; 18 | } 19 | 20 | final data = err.response?.data; 21 | 22 | final repository = context.read(); 23 | 24 | if (data == null || 25 | !(data is Map) || 26 | err.response?.statusCode == 401 && 27 | await repository.getRefreshToken() != null && 28 | !err.requestOptions.headers 29 | .containsKey(AuthTokenInterceptor.skipHeader)) { 30 | return super.onError(err, handler); 31 | } 32 | 33 | if (!err.requestOptions.headers.containsKey(skipHeader)) { 34 | showDialog( 35 | context: context, 36 | builder: (context) => AlertDialogWidget( 37 | title: data['error'] ?? data['message'], 38 | description: data['message'], 39 | ), 40 | ); 41 | } 42 | 43 | return super.onError(err, handler); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/circles_background.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CirclesBackground extends StatelessWidget { 4 | final Widget child; 5 | final Color backgroundColor; 6 | final Color topSmallCircleColor; 7 | final Color topMediumCircleColor; 8 | final Color topRightCircleColor; 9 | final Color bottomRightCircleColor; 10 | 11 | const CirclesBackground({ 12 | Key? key, 13 | required this.child, 14 | required this.backgroundColor, 15 | required this.topSmallCircleColor, 16 | required this.topMediumCircleColor, 17 | required this.topRightCircleColor, 18 | required this.bottomRightCircleColor, 19 | }) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | final size = MediaQuery.of(context).size; 24 | 25 | return Container( 26 | width: double.infinity, 27 | height: double.infinity, 28 | decoration: BoxDecoration(color: backgroundColor), 29 | child: Stack( 30 | children: [ 31 | Positioned( 32 | top: -16, 33 | left: 235, 34 | child: _CircularBox( 35 | width: 398, 36 | height: 398, 37 | color: topRightCircleColor, 38 | ), 39 | ), 40 | Positioned( 41 | top: -412, 42 | left: -184, 43 | child: _CircularBox( 44 | width: 700, 45 | height: 700, 46 | color: topMediumCircleColor, 47 | ), 48 | ), 49 | Positioned( 50 | top: -292, 51 | left: -163, 52 | child: _CircularBox( 53 | width: 398, 54 | height: 398, 55 | color: topSmallCircleColor, 56 | ), 57 | ), 58 | Positioned( 59 | top: size.height - (459 / 2), 60 | left: size.width - (459 / 2), 61 | child: _CircularBox( 62 | width: 459, 63 | height: 459, 64 | color: bottomRightCircleColor, 65 | ), 66 | ), 67 | SafeArea(child: this.child), 68 | ], 69 | ), 70 | ); 71 | } 72 | } 73 | 74 | class _CircularBox extends StatelessWidget { 75 | final double width; 76 | final double height; 77 | final Color color; 78 | 79 | const _CircularBox({ 80 | Key? key, 81 | required this.width, 82 | required this.height, 83 | required this.color, 84 | }) : super(key: key); 85 | 86 | @override 87 | Widget build(BuildContext context) { 88 | return Container( 89 | width: width, 90 | height: height, 91 | decoration: BoxDecoration( 92 | color: color, 93 | borderRadius: BorderRadius.circular(height), 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/dialog/alert_dialog_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AlertDialogWidget extends StatelessWidget { 4 | final String title; 5 | final dynamic description; 6 | final String? closeText; 7 | 8 | AlertDialogWidget({ 9 | Key? key, 10 | required this.title, 11 | required this.description, 12 | this.closeText, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return AlertDialog( 18 | title: Text(title), 19 | content: SingleChildScrollView( 20 | child: ListBody( 21 | children: description is List 22 | ? (description as List).map((text) => Text(text)).toList() 23 | : [Text(description)], 24 | ), 25 | ), 26 | actions: [ 27 | TextButton( 28 | child: Text(closeText ?? 'Close'), 29 | onPressed: () => Navigator.of(context).pop(), 30 | ), 31 | ], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/dialog/confirm_dialog_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ConfirmDialogWidget extends StatelessWidget { 4 | final String? title; 5 | final String? description; 6 | final String? closeText; 7 | final String? acceptText; 8 | 9 | ConfirmDialogWidget({ 10 | Key? key, 11 | this.title, 12 | this.description, 13 | this.closeText, 14 | this.acceptText, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return AlertDialog( 20 | title: Text(title ?? 'Are you sure?'), 21 | content: Text( 22 | description ?? 'You are not going to be able to undo this action'), 23 | actions: [ 24 | TextButton( 25 | child: Text(closeText ?? 'Close'), 26 | onPressed: () => Navigator.pop(context, false), 27 | ), 28 | TextButton( 29 | child: Text(acceptText ?? 'Accept'), 30 | onPressed: () => Navigator.pop(context, true), 31 | ), 32 | ], 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/go_back_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:auth/src/features/home/views/screens/home_screen.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class GoBackButton extends StatelessWidget { 5 | const GoBackButton({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | final onTap = () async { 10 | if (!await Navigator.maybePop(context)) { 11 | Navigator.of(context).pushNamedAndRemoveUntil( 12 | HomeScreen.routeName, 13 | (_) => false, 14 | ); 15 | } 16 | }; 17 | 18 | return GestureDetector( 19 | onTap: onTap, 20 | child: Container( 21 | padding: EdgeInsets.all(10), 22 | child: Column( 23 | children: [ 24 | Transform.scale( 25 | scale: 2, 26 | child: Icon( 27 | Icons.chevron_left_sharp, 28 | color: Colors.white, 29 | ), 30 | ), 31 | ], 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/main_text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MainTextField extends StatelessWidget { 4 | final ValueChanged? onChanged; 5 | final VoidCallback? onEditingComplete; 6 | final ValueChanged? onSubmitted; 7 | final String label; 8 | final TextEditingController? controller; 9 | final bool passwordField; 10 | final bool emailField; 11 | final bool messageField; 12 | final bool usernameField; 13 | final Color? textColor; 14 | 15 | const MainTextField({ 16 | Key? key, 17 | this.onChanged, 18 | this.controller, 19 | this.onEditingComplete, 20 | this.onSubmitted, 21 | this.passwordField = false, 22 | this.emailField = false, 23 | this.messageField = false, 24 | this.usernameField = false, 25 | this.textColor, 26 | required this.label, 27 | }) : super(key: key); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return TextField( 32 | decoration: InputDecoration( 33 | filled: true, 34 | fillColor: Color.fromRGBO(0, 0, 0, 0.1), 35 | border: OutlineInputBorder(), 36 | hintText: label, 37 | focusedBorder: OutlineInputBorder( 38 | borderSide: BorderSide(color: Colors.transparent), 39 | borderRadius: BorderRadius.circular(25), 40 | ), 41 | enabledBorder: UnderlineInputBorder( 42 | borderSide: BorderSide(color: Colors.transparent), 43 | borderRadius: BorderRadius.circular(25), 44 | ), 45 | contentPadding: EdgeInsets.all(21), 46 | hintStyle: TextStyle(color: textColor), 47 | ), 48 | keyboardType: emailField 49 | ? TextInputType.emailAddress 50 | : messageField 51 | ? TextInputType.multiline 52 | : null, 53 | obscureText: passwordField, 54 | maxLength: passwordField ? 60 : null, 55 | autocorrect: !usernameField && !emailField, 56 | controller: controller, 57 | onChanged: onChanged, 58 | onEditingComplete: onEditingComplete, 59 | onSubmitted: onSubmitted, 60 | cursorColor: Color.fromRGBO(0, 0, 0, 0.1), 61 | style: TextStyle(color: textColor), 62 | maxLines: this.messageField == true ? null : 1, 63 | textInputAction: onEditingComplete != null 64 | ? TextInputAction.next 65 | : messageField 66 | ? TextInputAction.newline 67 | : null, 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/next_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NextButton extends StatelessWidget { 4 | final VoidCallback? onPressed; 5 | 6 | final bool? loading; 7 | 8 | const NextButton({Key? key, this.onPressed, this.loading}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | padding: EdgeInsets.all(10), 14 | decoration: BoxDecoration( 15 | color: Theme.of(context).primaryColor, 16 | borderRadius: BorderRadius.circular(50), 17 | ), 18 | child: IconButton( 19 | color: Colors.white, 20 | onPressed: onPressed, 21 | icon: loading == true 22 | ? CircularProgressIndicator( 23 | color: Colors.white, 24 | strokeWidth: 2, 25 | ) 26 | : Icon(Icons.arrow_forward), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/scroll_close_keyboard.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScrollCloseKeyboard extends StatelessWidget { 4 | final Widget child; 5 | 6 | const ScrollCloseKeyboard({Key? key, required this.child}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return GestureDetector( 11 | onVerticalDragUpdate: (details) { 12 | if (details.delta.distance > 20) { 13 | FocusScope.of(context).unfocus(); 14 | } 15 | }, 16 | child: child, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/scrollable_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScrollableForm extends StatelessWidget { 4 | final List children; 5 | 6 | final EdgeInsetsGeometry? padding; 7 | 8 | const ScrollableForm({ 9 | Key? key, 10 | required this.children, 11 | this.padding, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Flexible( 17 | child: Form( 18 | child: Container( 19 | padding: padding, 20 | child: ListView( 21 | keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, 22 | children: children, 23 | ), 24 | ), 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/typing_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class TypingIndicator extends StatefulWidget { 6 | const TypingIndicator({ 7 | Key? key, 8 | this.bubbleColor = const Color(0xFF646b7f), 9 | this.flashingCircleDarkColor = const Color(0xFF333333), 10 | this.flashingCircleBrightColor = const Color(0xFFaec1dd), 11 | }) : super(key: key); 12 | 13 | final Color bubbleColor; 14 | final Color flashingCircleDarkColor; 15 | final Color flashingCircleBrightColor; 16 | 17 | @override 18 | _TypingIndicatorState createState() => _TypingIndicatorState(); 19 | } 20 | 21 | class _TypingIndicatorState extends State 22 | with TickerProviderStateMixin { 23 | late AnimationController _appearanceController; 24 | 25 | late Animation _largeBubbleAnimation; 26 | 27 | late AnimationController _repeatingController; 28 | final List _dotIntervals = const [ 29 | Interval(0.25, 0.8), 30 | Interval(0.35, 0.9), 31 | Interval(0.45, 1.0), 32 | ]; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | 38 | _appearanceController = AnimationController( 39 | vsync: this, 40 | )..addListener(() { 41 | setState(() {}); 42 | }); 43 | 44 | _largeBubbleAnimation = CurvedAnimation( 45 | parent: _appearanceController, 46 | curve: const Interval(0.3, 1.0, curve: Curves.elasticOut), 47 | reverseCurve: const Interval(0.5, 1.0, curve: Curves.easeOut), 48 | ); 49 | 50 | _repeatingController = AnimationController( 51 | vsync: this, 52 | duration: const Duration(milliseconds: 1500), 53 | ); 54 | 55 | _showIndicator(); 56 | } 57 | 58 | @override 59 | void dispose() { 60 | _appearanceController.dispose(); 61 | _repeatingController.dispose(); 62 | super.dispose(); 63 | } 64 | 65 | void _showIndicator() { 66 | _appearanceController 67 | ..duration = const Duration(milliseconds: 750) 68 | ..forward(); 69 | _repeatingController.repeat(); 70 | } 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | return Stack( 75 | children: [ 76 | _buildAnimatedBubble( 77 | animation: _largeBubbleAnimation, 78 | left: 12, 79 | bottom: 12, 80 | bubble: _buildStatusBubble(), 81 | ), 82 | ], 83 | ); 84 | } 85 | 86 | Widget _buildAnimatedBubble({ 87 | required Animation animation, 88 | required double left, 89 | required double bottom, 90 | required Widget bubble, 91 | }) { 92 | return AnimatedBuilder( 93 | animation: animation, 94 | builder: (context, child) { 95 | return Transform.scale( 96 | scale: animation.value, 97 | alignment: Alignment.bottomLeft, 98 | child: child, 99 | ); 100 | }, 101 | child: bubble, 102 | ); 103 | } 104 | 105 | Widget _buildStatusBubble() { 106 | return Container( 107 | width: 85, 108 | height: 18, 109 | padding: const EdgeInsets.symmetric(horizontal: 8), 110 | child: Row( 111 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 112 | children: [ 113 | _buildFlashingCircle(0), 114 | _buildFlashingCircle(1), 115 | _buildFlashingCircle(2), 116 | ], 117 | ), 118 | ); 119 | } 120 | 121 | Widget _buildFlashingCircle(int index) { 122 | return AnimatedBuilder( 123 | animation: _repeatingController, 124 | builder: (context, child) { 125 | final circleFlashPercent = 126 | _dotIntervals[index].transform(_repeatingController.value); 127 | final circleColorPercent = sin(pi * circleFlashPercent); 128 | 129 | return Container( 130 | width: 12, 131 | height: 12, 132 | decoration: BoxDecoration( 133 | shape: BoxShape.circle, 134 | color: Color.lerp(widget.flashingCircleDarkColor, 135 | widget.flashingCircleBrightColor, circleColorPercent), 136 | ), 137 | ); 138 | }, 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/underlined_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class UnderlinedButton extends StatelessWidget { 4 | final Widget child; 5 | 6 | final VoidCallback? onPressed; 7 | 8 | final Color color; 9 | 10 | final Color? textColor; 11 | 12 | const UnderlinedButton({ 13 | Key? key, 14 | required this.child, 15 | this.onPressed, 16 | this.textColor, 17 | required this.color, 18 | }) : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return TextButton( 23 | onPressed: onPressed, 24 | child: Stack( 25 | children: [ 26 | Positioned( 27 | bottom: 0, 28 | child: Container( 29 | color: color, 30 | height: 10, 31 | width: 500, 32 | ), 33 | ), 34 | Padding( 35 | padding: EdgeInsets.only(left: 5, right: 5), 36 | child: child, 37 | ) 38 | ], 39 | ), 40 | style: TextButton.styleFrom( 41 | primary: textColor ?? Colors.black, 42 | textStyle: TextStyle( 43 | color: textColor ?? Colors.black, 44 | fontWeight: FontWeight.bold, 45 | fontSize: 17, 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/shared/views/widgets/user_status.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class UserStatus extends StatelessWidget { 4 | final bool online; 5 | 6 | const UserStatus({Key? key, required this.online}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | width: 10, 12 | height: 10, 13 | padding: EdgeInsets.all(5), 14 | decoration: BoxDecoration( 15 | color: online ? Colors.green : Colors.red, 16 | borderRadius: BorderRadius.circular(20), 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: auth 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: '>=2.12.0 <3.0.0' 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | # The following adds the Cupertino Icons font to your application. 28 | # Use with the CupertinoIcons class for iOS style icons. 29 | cupertino_icons: ^1.0.2 30 | flutter_secure_storage: ^4.2.1 31 | provider: ^6.0.1 32 | flutter_launcher_icons: ^0.9.2 33 | dio: ^4.0.0 34 | flutter_facebook_auth: ^3.5.2 35 | flutter_signin_button: ^2.0.0 36 | # google_sign_in: ^5.1.1 37 | sign_in_with_apple: ^3.2.0 38 | equatable: ^2.0.3 39 | socket_io_client: ^2.0.0-beta.4-nullsafety.0 40 | intl: ^0.17.0 41 | audioplayers: ^0.20.1 42 | firebase_core: ^1.7.0 43 | firebase_messaging: ^10.0.8 44 | firebase_analytics: ^8.3.3 45 | google_sign_in: ^5.1.1 46 | flutter_bloc: ^8.0.0 47 | bloc: ^8.0.1 48 | 49 | flutter_icons: 50 | android: true 51 | ios: true 52 | image_path: 'assets/icon.png' 53 | 54 | dev_dependencies: 55 | flutter_test: 56 | sdk: flutter 57 | 58 | # For information on the generic Dart part of this file, see the 59 | # following page: https://dart.dev/tools/pub/pubspec 60 | 61 | # The following section is specific to Flutter. 62 | flutter: 63 | # The following line ensures that the Material Icons font is 64 | # included with your application, so that you can use the icons in 65 | # the material Icons class. 66 | uses-material-design: true 67 | 68 | # To add assets to your application, add an assets section, like this: 69 | assets: 70 | - assets/ 71 | - assets/tones/ 72 | 73 | # An image asset can refer to one or more resolution-specific "variants", see 74 | # https://flutter.dev/assets-and-images/#resolution-aware. 75 | 76 | # For details regarding adding assets from package dependencies, see 77 | # https://flutter.dev/assets-and-images/#from-packages 78 | 79 | # To add custom fonts to your application, add a fonts section here, 80 | # in this "flutter" section. Each entry in this list should have a 81 | # "family" key with the font family name, and a "fonts" key with a 82 | # list giving the asset and other descriptors for the font. For 83 | # example: 84 | # fonts: 85 | # - family: Schyler 86 | # fonts: 87 | # - asset: fonts/Schyler-Regular.ttf 88 | # - asset: fonts/Schyler-Italic.ttf 89 | # style: italic 90 | # - family: Trajan Pro 91 | # fonts: 92 | # - asset: fonts/TrajanPro.ttf 93 | # - asset: fonts/TrajanPro_Bold.ttf 94 | # weight: 700 95 | # 96 | # For details regarding fonts from package dependencies, 97 | # see https://flutter.dev/custom-fonts/#from-packages 98 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenzelCode/flutter-auth/69b92a34c56ab0583370b4b281c054a4c7abbe9c/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | auth 27 | 28 | 29 | 30 | 33 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "short_name": "auth", 4 | "start_url": ".", 5 | "display": "standalone", 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 | --------------------------------------------------------------------------------