├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── gradle.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── google-services.json
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── firebasewithmvvm
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── firebasewithmvvm
│ │ │ ├── MainActivity.kt
│ │ │ ├── MyApplication.kt
│ │ │ ├── data
│ │ │ ├── model
│ │ │ │ ├── Note.kt
│ │ │ │ ├── Task.kt
│ │ │ │ └── User.kt
│ │ │ └── repository
│ │ │ │ ├── AuthRepository.kt
│ │ │ │ ├── AuthRepositoryImp.kt
│ │ │ │ ├── NoteRepository.kt
│ │ │ │ ├── NoteRepositoryImp.kt
│ │ │ │ ├── TaskRepository.kt
│ │ │ │ └── TaskRepositoryImp.kt
│ │ │ ├── di
│ │ │ ├── AppModule.kt
│ │ │ ├── FirebaseModule.kt
│ │ │ └── RepositoryModule.kt
│ │ │ ├── ui
│ │ │ ├── auth
│ │ │ │ ├── AuthViewModel.kt
│ │ │ │ ├── ForgotPasswordFragment.kt
│ │ │ │ ├── LoginFragment.kt
│ │ │ │ └── RegisterFragment.kt
│ │ │ ├── home
│ │ │ │ ├── HomeFragment.kt
│ │ │ │ └── HomePagerAdapter.kt
│ │ │ ├── note
│ │ │ │ ├── ImageListingAdapter.kt
│ │ │ │ ├── NoteDetailFragment.kt
│ │ │ │ ├── NoteListingAdapter.kt
│ │ │ │ ├── NoteListingFragment.kt
│ │ │ │ └── NoteViewModel.kt
│ │ │ └── task
│ │ │ │ ├── CreateTaskFragment.kt
│ │ │ │ ├── TaskListingAdapter.kt
│ │ │ │ ├── TaskListingFragment.kt
│ │ │ │ └── TaskViewModel.kt
│ │ │ └── util
│ │ │ ├── Constants.kt
│ │ │ ├── Extensions.kt
│ │ │ └── UiState.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── black_rect_bg.xml
│ │ ├── black_white_rect_bg.xml
│ │ ├── ic_baseline_add_24.xml
│ │ ├── ic_baseline_add_circle_24.xml
│ │ ├── ic_baseline_arrow_back_ios_24.xml
│ │ ├── ic_baseline_cancel_24.xml
│ │ ├── ic_baseline_check_24.xml
│ │ ├── ic_baseline_delete_24.xml
│ │ ├── ic_baseline_edit_24.xml
│ │ ├── ic_baseline_exit_to_app_24.xml
│ │ └── ic_launcher_background.xml
│ │ ├── font
│ │ ├── titillium_web_black.ttf
│ │ ├── titillium_web_bold.ttf
│ │ ├── titillium_web_light.ttf
│ │ └── titillium_web_regular.ttf
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── add_tag_dialog.xml
│ │ ├── fragment_create_task.xml
│ │ ├── fragment_forgot_password.xml
│ │ ├── fragment_home.xml
│ │ ├── fragment_login.xml
│ │ ├── fragment_note_detail.xml
│ │ ├── fragment_note_listing.xml
│ │ ├── fragment_register.xml
│ │ ├── fragment_task_listing.xml
│ │ ├── image_layout.xml
│ │ ├── item_chip.xml
│ │ ├── item_note_layout.xml
│ │ ├── item_tab_layout.xml
│ │ └── task_layout.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── navigation
│ │ ├── app_navigation.xml
│ │ └── home_navigation.xml
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── firebasewithmvvm
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── note-taking-detail.png
├── note-taking-forgotpassword.png
├── note-taking-listing.png
├── note-taking-login.png
└── note-taking-register.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Firebase with MVVM
2 |
3 | In this project we will learn about Firebase Implementation with MVVM Architecture. It is a basic level Course and will go with project based approach so can understand better that how the things are working.
4 |
5 | ## Topics
6 |
7 | - [Introduction](https://www.youtube.com/watch?v=MNomjZf-8C8&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=1&t=71s)
8 | - [Github Integration](https://www.youtube.com/watch?v=-os2YCijeIA&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=2)
9 | - [Firebase Implementation](https://www.youtube.com/watch?v=jNrSNyNGskg&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=3)
10 | - [Firestore Connection Testing](https://www.youtube.com/watch?v=vSFCqzc2dF8&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=4)
11 | - [Jetpack Navigation Component](https://www.youtube.com/watch?v=tOYHlI_by64&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=5)
12 | - [MVVM Implementation](https://www.youtube.com/watch?v=jy8QzgW1oYk&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=7)
13 | - [Dagger Hilt](https://www.youtube.com/watch?v=K65IyCMUbZg&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=8)
14 | - [Resource Class to Manage Ui States](https://www.youtube.com/watch?v=kv1YWtl9ILM&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=9)
15 | - [Add Note & Listing Notes in RecyclerView](https://www.youtube.com/watch?v=7ZNk87k441U&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=10)
16 | - [Update Note](https://www.youtube.com/watch?v=oHvPmOEKpjc&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=11)
17 | - [Delete Note](https://www.youtube.com/watch?v=ngvPIiDRRv0&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=12)
18 | - [Migrating old design to new (Branch: modification/design)](https://www.youtube.com/watch?v=DYCWfCOohsQ&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=14)
19 | - [Explaining old design to new design (Branch: modification/design)](https://www.youtube.com/watch?v=dDvcGichH04&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=15)
20 | - [Divider Line Overlapping Chipgroup Ui Bug (Branch: modification/design)](https://www.youtube.com/watch?v=T9mk9ivhIOw&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=16)
21 | - [Register with email/password (Branch: feature/register)](https://www.youtube.com/watch?v=xsEDL0Y3Ays&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=17)
22 | - [Login with email/password (Branch: feature/login)](https://www.youtube.com/watch?v=r48IHaYEJTk&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=18)
23 | - [Logout (Branch: feature/logout)](https://www.youtube.com/watch?v=uUMFHZEzGQE)
24 | - [Session (Branch: feature/session-handling)](https://youtu.be/uP2R6Trij4A)
25 | - [WhereEqualTo User Notes (Branch: feature/user-notes)](https://youtu.be/UWPz6344gb0)
26 | - [Pick Image (Branch: feature/pick-images)](https://youtu.be/UWPz6344gb0)
27 | - [Upload single image to Firebase Storage Notes (Branch: feature/upload-single-image)](https://youtu.be/UWPz6344gb0)
28 | - [Upload multiple images to Firebase Storage (Branch: feature/upload-multiple-images)](https://youtu.be/UWPz6344gb0)
29 | - [Viewpager2, TabLayout & Navigation Global Action (Branch: design/tablayout-viewpager2)](https://youtu.be/meJIEA_RalU)
30 | - [Create Task using Firebase Database (Branch: feature/create-task)](https://youtu.be/TJOw6vFSPrE)
31 | - [Listing Task using Firebase Database (Branch: feature/task-listing)](https://www.youtube.com/watch?v=tZ7tSXamPn0)
32 |
33 |
34 | ## Branches
35 | - **master** has complete code with new design.
36 | - **feature/navigation-component** has related to navigation component
37 | - **dagger-hilt-impl** has related to dagger hilt implementation
38 | - **feature/add-note** has related to add note
39 | - **feature/delete-note** has related to delete note
40 | - **old-design** has complete old code with old design.
41 | - **modification/design** has complete code with new design, refacotred code and modification in logic etc.
42 | - **feature/regsiter** has related to register with email and password.
43 | - **feature/login** has related to login with email and password.
44 | - **feature/logout** has related to logout.
45 | - **feature/session-handling** has related to session handling
46 | - **feature/user-notes** has related to getting notes based on user id to see its own notes.
47 | - **(feature/pick-images)** has related to pick image from gallery.
48 | - **(feature/upload-single-image)** has related to upload single image to firebase storage.
49 | - **(feature/upload-multiple-images)** has related to upload multiple images to firebase storage.
50 | - **(design/tablayout-viewpager2)** has related to viewpager2, tablayout and jetpack navigation global action.
51 | - **(feature/create-task)** has related to creating a task using firebase database
52 | - **(feature/task-listing)** has related to listing of task from firebase database
53 |
54 |
55 | ## Youtube Playlist
56 | [Click for the whole course playlist](https://www.youtube.com/playlist?list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL)
57 |
58 |
59 | ## Articles
60 | - [Upload multiple images to Firebase Storage](https://realtimecoding.com/uploading-multiple-files-to-firebasestorage)
61 |
62 | ## Demo
63 |
64 |
65 |
66 | Login
67 | Register
68 | Forgot Password
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Note Listing Screen
81 | Note Detail/Create/Update Screen
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'com.google.gms.google-services'
5 | id 'dagger.hilt.android.plugin'
6 | id 'kotlin-kapt'
7 | id 'kotlin-parcelize'
8 | }
9 |
10 | android {
11 | compileSdk 32
12 |
13 | defaultConfig {
14 | applicationId "com.example.firebasewithmvvm"
15 | minSdk 21
16 | targetSdk 32
17 | versionCode 1
18 | versionName "1.0"
19 |
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 |
23 | buildFeatures{
24 | viewBinding true
25 | }
26 |
27 | buildTypes {
28 | release {
29 | minifyEnabled false
30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
31 | }
32 | }
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_1_8
35 | targetCompatibility JavaVersion.VERSION_1_8
36 | }
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 | }
41 |
42 | dependencies {
43 |
44 | implementation 'androidx.core:core-ktx:1.7.0'
45 | implementation 'androidx.appcompat:appcompat:1.4.1'
46 | implementation 'com.google.android.material:material:1.6.0'
47 | implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
48 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
49 |
50 | //Jetpack Navigation Component
51 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
52 | implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
53 | implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
54 |
55 | // LiveData
56 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
57 | //fragment
58 | implementation 'androidx.fragment:fragment-ktx:1.4.1'
59 |
60 | //Dagger Hilt
61 | implementation 'com.google.dagger:hilt-android:2.40.5'
62 | kapt 'com.google.dagger:hilt-android-compiler:2.40.5'
63 | implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
64 | kapt 'androidx.hilt:hilt-compiler:1.0.0'
65 |
66 | //Testing
67 | testImplementation 'junit:junit:4.13.2'
68 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
69 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
70 |
71 | //Firebase
72 | implementation 'com.google.firebase:firebase-firestore:24.1.2'
73 | implementation 'com.google.firebase:firebase-auth:19.2.0'
74 | implementation 'com.google.firebase:firebase-storage-ktx:20.0.1'
75 | implementation 'com.google.firebase:firebase-database-ktx:20.0.5'
76 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.1"
77 |
78 | //Gson
79 | implementation 'com.google.code.gson:gson:2.8.9'
80 |
81 | //ImagePicker
82 | implementation 'com.github.dhaval2404:imagepicker:2.1'
83 |
84 | }
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "1021082332979",
4 | "firebase_url": "https://fir-withmvvm-e94f9-default-rtdb.firebaseio.com",
5 | "project_id": "fir-withmvvm-e94f9",
6 | "storage_bucket": "fir-withmvvm-e94f9.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:1021082332979:android:f7d6017747b75214bfd846",
12 | "android_client_info": {
13 | "package_name": "com.example.firebasewithmvvm"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "1021082332979-mu58dugjrk432rcvtucf5pabplt7ba5q.apps.googleusercontent.com",
19 | "client_type": 1,
20 | "android_info": {
21 | "package_name": "com.example.firebasewithmvvm",
22 | "certificate_hash": "df7df0c00a41127195a615f7a254e36f8363ac8f"
23 | }
24 | },
25 | {
26 | "client_id": "1021082332979-ukbbvg9og0fhl78npjsn5si03svg0fdj.apps.googleusercontent.com",
27 | "client_type": 3
28 | }
29 | ],
30 | "api_key": [
31 | {
32 | "current_key": "AIzaSyBUezs8nPuYl5dxWwnlfQ9OTpUTtl9yDdA"
33 | }
34 | ],
35 | "services": {
36 | "appinvite_service": {
37 | "other_platform_oauth_client": [
38 | {
39 | "client_id": "1021082332979-ukbbvg9og0fhl78npjsn5si03svg0fdj.apps.googleusercontent.com",
40 | "client_type": 3
41 | }
42 | ]
43 | }
44 | }
45 | }
46 | ],
47 | "configuration_version": "1"
48 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/firebasewithmvvm/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.firebasewithmvvm", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.navigation.NavController
7 | import androidx.navigation.NavGraph
8 | import androidx.navigation.findNavController
9 | import androidx.navigation.fragment.NavHostFragment
10 | import com.google.android.gms.tasks.OnFailureListener
11 | import com.google.android.gms.tasks.OnSuccessListener
12 | import com.google.firebase.firestore.DocumentReference
13 | import com.google.firebase.firestore.FirebaseFirestore
14 | import dagger.hilt.android.AndroidEntryPoint
15 |
16 |
17 | @AndroidEntryPoint
18 | class MainActivity : AppCompatActivity() {
19 |
20 | lateinit var navController: NavController
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContentView(R.layout.activity_main)
25 | val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
26 | navController = navHostFragment.navController
27 | }
28 |
29 | override fun onBackPressed() {
30 | super.onBackPressed()
31 | if (navController.currentDestination?.id == R.id.loginFragment){
32 | moveTaskToBack(true)
33 | }else{
34 | super.onBackPressed()
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/MyApplication.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class MyApplication: Application()
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/model/Note.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.model
2 |
3 | import android.os.Parcelable
4 | import com.google.firebase.firestore.ServerTimestamp
5 | import kotlinx.parcelize.Parcelize
6 | import java.util.*
7 |
8 | @Parcelize
9 | data class Note(
10 | var id: String = "",
11 | var user_id: String = "",
12 | val title: String = "",
13 | val description: String = "",
14 | val tags: MutableList = arrayListOf(),
15 | val images: List = arrayListOf(),
16 | @ServerTimestamp
17 | val date: Date = Date(),
18 | ) : Parcelable
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/model/Task.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.model
2 |
3 | import android.os.Parcelable
4 | import com.google.firebase.firestore.ServerTimestamp
5 | import kotlinx.parcelize.Parcelize
6 | import java.util.*
7 |
8 | @Parcelize
9 | data class Task(
10 | var id: String = "",
11 | var user_id: String = "",
12 | var description: String = "",
13 | val date: String = "",
14 | ) : Parcelable
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/model/User.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.model
2 |
3 |
4 | data class User(
5 | var id: String = "",
6 | val first_name: String = "",
7 | val last_name: String = "",
8 | val job_title: String = "",
9 | val email: String = "",
10 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/repository/AuthRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.repository
2 |
3 | import com.example.firebasewithmvvm.data.model.Note
4 | import com.example.firebasewithmvvm.data.model.User
5 | import com.example.firebasewithmvvm.util.UiState
6 |
7 | interface AuthRepository {
8 | fun registerUser(email: String, password: String, user: User, result: (UiState) -> Unit)
9 | fun updateUserInfo(user: User, result: (UiState) -> Unit)
10 | fun loginUser(email: String, password: String, result: (UiState) -> Unit)
11 | fun forgotPassword(email: String, result: (UiState) -> Unit)
12 | fun logout(result: () -> Unit)
13 | fun storeSession(id: String, result: (User?) -> Unit)
14 | fun getSession(result: (User?) -> Unit)
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/repository/AuthRepositoryImp.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.repository
2 |
3 | import android.content.SharedPreferences
4 | import com.example.firebasewithmvvm.data.model.User
5 | import com.example.firebasewithmvvm.util.FireStoreCollection
6 | import com.example.firebasewithmvvm.util.SharedPrefConstants
7 | import com.example.firebasewithmvvm.util.UiState
8 | import com.google.firebase.auth.*
9 | import com.google.firebase.firestore.FirebaseFirestore
10 | import com.google.gson.Gson
11 |
12 | class AuthRepositoryImp(
13 | val auth: FirebaseAuth,
14 | val database: FirebaseFirestore,
15 | val appPreferences: SharedPreferences,
16 | val gson: Gson
17 | ) : AuthRepository {
18 |
19 | override fun registerUser(
20 | email: String,
21 | password: String,
22 | user: User, result: (UiState) -> Unit
23 | ) {
24 | auth.createUserWithEmailAndPassword(email,password)
25 | .addOnCompleteListener {
26 | if (it.isSuccessful){
27 | user.id = it.result.user?.uid ?: ""
28 | updateUserInfo(user) { state ->
29 | when(state){
30 | is UiState.Success -> {
31 | storeSession(id = it.result.user?.uid ?: "") {
32 | if (it == null){
33 | result.invoke(UiState.Failure("User register successfully but session failed to store"))
34 | }else{
35 | result.invoke(
36 | UiState.Success("User register successfully!")
37 | )
38 | }
39 | }
40 | }
41 | is UiState.Failure -> {
42 | result.invoke(UiState.Failure(state.error))
43 | }
44 | }
45 | }
46 | }else{
47 | try {
48 | throw it.exception ?: java.lang.Exception("Invalid authentication")
49 | } catch (e: FirebaseAuthWeakPasswordException) {
50 | result.invoke(UiState.Failure("Authentication failed, Password should be at least 6 characters"))
51 | } catch (e: FirebaseAuthInvalidCredentialsException) {
52 | result.invoke(UiState.Failure("Authentication failed, Invalid email entered"))
53 | } catch (e: FirebaseAuthUserCollisionException) {
54 | result.invoke(UiState.Failure("Authentication failed, Email already registered."))
55 | } catch (e: Exception) {
56 | result.invoke(UiState.Failure(e.message))
57 | }
58 | }
59 | }
60 | .addOnFailureListener {
61 | result.invoke(
62 | UiState.Failure(
63 | it.localizedMessage
64 | )
65 | )
66 | }
67 | }
68 |
69 | override fun updateUserInfo(user: User, result: (UiState) -> Unit) {
70 | val document = database.collection(FireStoreCollection.USER).document(user.id)
71 | document
72 | .set(user)
73 | .addOnSuccessListener {
74 | result.invoke(
75 | UiState.Success("User has been update successfully")
76 | )
77 | }
78 | .addOnFailureListener {
79 | result.invoke(
80 | UiState.Failure(
81 | it.localizedMessage
82 | )
83 | )
84 | }
85 | }
86 |
87 | override fun loginUser(
88 | email: String,
89 | password: String,
90 | result: (UiState) -> Unit) {
91 | auth.signInWithEmailAndPassword(email,password)
92 | .addOnCompleteListener { task ->
93 | if (task.isSuccessful) {
94 | storeSession(id = task.result.user?.uid ?: ""){
95 | if (it == null){
96 | result.invoke(UiState.Failure("Failed to store local session"))
97 | }else{
98 | result.invoke(UiState.Success("Login successfully!"))
99 | }
100 | }
101 | }
102 | }.addOnFailureListener {
103 | result.invoke(UiState.Failure("Authentication failed, Check email and password"))
104 | }
105 | }
106 |
107 | override fun forgotPassword(email: String, result: (UiState) -> Unit) {
108 | auth.sendPasswordResetEmail(email)
109 | .addOnCompleteListener { task ->
110 | if (task.isSuccessful) {
111 | result.invoke(UiState.Success("Email has been sent"))
112 |
113 | } else {
114 | result.invoke(UiState.Failure(task.exception?.message))
115 | }
116 | }.addOnFailureListener {
117 | result.invoke(UiState.Failure("Authentication failed, Check email"))
118 | }
119 | }
120 |
121 | override fun logout(result: () -> Unit) {
122 | auth.signOut()
123 | appPreferences.edit().putString(SharedPrefConstants.USER_SESSION,null).apply()
124 | result.invoke()
125 | }
126 |
127 | override fun storeSession(id: String, result: (User?) -> Unit) {
128 | database.collection(FireStoreCollection.USER).document(id)
129 | .get()
130 | .addOnCompleteListener {
131 | if (it.isSuccessful){
132 | val user = it.result.toObject(User::class.java)
133 | appPreferences.edit().putString(SharedPrefConstants.USER_SESSION,gson.toJson(user)).apply()
134 | result.invoke(user)
135 | }else{
136 | result.invoke(null)
137 | }
138 | }
139 | .addOnFailureListener {
140 | result.invoke(null)
141 | }
142 | }
143 |
144 | override fun getSession(result: (User?) -> Unit) {
145 | val user_str = appPreferences.getString(SharedPrefConstants.USER_SESSION,null)
146 | if (user_str == null){
147 | result.invoke(null)
148 | }else{
149 | val user = gson.fromJson(user_str,User::class.java)
150 | result.invoke(user)
151 | }
152 | }
153 |
154 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/repository/NoteRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.repository
2 |
3 | import android.net.Uri
4 | import com.example.firebasewithmvvm.data.model.Note
5 | import com.example.firebasewithmvvm.data.model.User
6 | import com.example.firebasewithmvvm.util.UiState
7 |
8 | interface NoteRepository {
9 | fun getNotes(user: User?, result: (UiState>) -> Unit)
10 | fun addNote(note: Note, result: (UiState>) -> Unit)
11 | fun updateNote(note: Note, result: (UiState) -> Unit)
12 | fun deleteNote(note: Note, result: (UiState) -> Unit)
13 | suspend fun uploadSingleFile(fileUri: Uri, onResult: (UiState) -> Unit)
14 | suspend fun uploadMultipleFile(fileUri: List, onResult: (UiState>) -> Unit)
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/repository/NoteRepositoryImp.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.repository
2 |
3 | import android.content.SharedPreferences
4 | import android.net.Uri
5 | import com.example.firebasewithmvvm.data.model.Note
6 | import com.example.firebasewithmvvm.data.model.User
7 | import com.example.firebasewithmvvm.util.FireStoreCollection
8 | import com.example.firebasewithmvvm.util.FireStoreDocumentField
9 | import com.example.firebasewithmvvm.util.FirebaseStorageConstants.NOTE_IMAGES
10 | import com.example.firebasewithmvvm.util.SharedPrefConstants
11 | import com.example.firebasewithmvvm.util.UiState
12 | import com.google.firebase.FirebaseException
13 | import com.google.firebase.firestore.FirebaseFirestore
14 | import com.google.firebase.firestore.FirebaseFirestoreException
15 | import com.google.firebase.firestore.Query
16 | import com.google.firebase.storage.StorageReference
17 | import kotlinx.coroutines.Dispatchers
18 | import kotlinx.coroutines.async
19 | import kotlinx.coroutines.awaitAll
20 | import kotlinx.coroutines.tasks.await
21 | import kotlinx.coroutines.withContext
22 |
23 | class NoteRepositoryImp(
24 | val database: FirebaseFirestore,
25 | val storageReference: StorageReference
26 | ) : NoteRepository {
27 |
28 | override fun getNotes(user: User?, result: (UiState>) -> Unit) {
29 | database.collection(FireStoreCollection.NOTE)
30 | .whereEqualTo(FireStoreDocumentField.USER_ID,user?.id)
31 | .orderBy(FireStoreDocumentField.DATE, Query.Direction.DESCENDING)
32 | .get()
33 | .addOnSuccessListener {
34 | val notes = arrayListOf()
35 | for (document in it) {
36 | val note = document.toObject(Note::class.java)
37 | notes.add(note)
38 | }
39 | result.invoke(
40 | UiState.Success(notes)
41 | )
42 | }
43 | .addOnFailureListener {
44 | result.invoke(
45 | UiState.Failure(
46 | it.localizedMessage
47 | )
48 | )
49 | }
50 | }
51 |
52 | override fun addNote(note: Note, result: (UiState>) -> Unit) {
53 | val document = database.collection(FireStoreCollection.NOTE).document()
54 | note.id = document.id
55 | document
56 | .set(note)
57 | .addOnSuccessListener {
58 | result.invoke(
59 | UiState.Success(Pair(note,"Note has been created successfully"))
60 | )
61 | }
62 | .addOnFailureListener {
63 | result.invoke(
64 | UiState.Failure(
65 | it.localizedMessage
66 | )
67 | )
68 | }
69 | }
70 |
71 | override fun updateNote(note: Note, result: (UiState) -> Unit) {
72 | val document = database.collection(FireStoreCollection.NOTE).document(note.id)
73 | document
74 | .set(note)
75 | .addOnSuccessListener {
76 | result.invoke(
77 | UiState.Success("Note has been update successfully")
78 | )
79 | }
80 | .addOnFailureListener {
81 | result.invoke(
82 | UiState.Failure(
83 | it.localizedMessage
84 | )
85 | )
86 | }
87 | }
88 |
89 | override fun deleteNote(note: Note, result: (UiState) -> Unit) {
90 | database.collection(FireStoreCollection.NOTE).document(note.id)
91 | .delete()
92 | .addOnSuccessListener {
93 | result.invoke(UiState.Success("Note successfully deleted!"))
94 | }
95 | .addOnFailureListener { e ->
96 | result.invoke(UiState.Failure(e.message))
97 | }
98 | }
99 |
100 | override suspend fun uploadSingleFile(fileUri: Uri, onResult: (UiState) -> Unit) {
101 | try {
102 | val uri: Uri = withContext(Dispatchers.IO) {
103 | storageReference
104 | .putFile(fileUri)
105 | .await()
106 | .storage
107 | .downloadUrl
108 | .await()
109 | }
110 | onResult.invoke(UiState.Success(uri))
111 | } catch (e: FirebaseException){
112 | onResult.invoke(UiState.Failure(e.message))
113 | }catch (e: Exception){
114 | onResult.invoke(UiState.Failure(e.message))
115 | }
116 | }
117 |
118 | override suspend fun uploadMultipleFile(
119 | fileUri: List,
120 | onResult: (UiState>) -> Unit
121 | ) {
122 | try {
123 | val uri: List = withContext(Dispatchers.IO) {
124 | fileUri.map { image ->
125 | async {
126 | storageReference.child(NOTE_IMAGES).child(image.lastPathSegment ?: "${System.currentTimeMillis()}")
127 | .putFile(image)
128 | .await()
129 | .storage
130 | .downloadUrl
131 | .await()
132 | }
133 | }.awaitAll()
134 | }
135 | onResult.invoke(UiState.Success(uri))
136 | } catch (e: FirebaseException){
137 | onResult.invoke(UiState.Failure(e.message))
138 | }catch (e: Exception){
139 | onResult.invoke(UiState.Failure(e.message))
140 | }
141 | }
142 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/repository/TaskRepository.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.repository
2 |
3 | import com.example.firebasewithmvvm.data.model.Task
4 | import com.example.firebasewithmvvm.data.model.User
5 | import com.example.firebasewithmvvm.util.UiState
6 |
7 | interface TaskRepository {
8 | fun addTask(task: Task, result: (UiState>) -> Unit)
9 | fun updateTask(task: Task, result: (UiState>) -> Unit)
10 | fun deleteTask(task: Task, result: (UiState>) -> Unit)
11 | fun getTasks(user: User?, result: (UiState>) -> Unit)
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/data/repository/TaskRepositoryImp.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.data.repository
2 |
3 | import com.example.firebasewithmvvm.data.model.Note
4 | import com.example.firebasewithmvvm.data.model.Task
5 | import com.example.firebasewithmvvm.data.model.User
6 | import com.example.firebasewithmvvm.util.FireDatabase
7 | import com.example.firebasewithmvvm.util.UiState
8 | import com.google.firebase.database.FirebaseDatabase
9 |
10 | class TaskRepositoryImp(
11 | val database: FirebaseDatabase
12 | ) : TaskRepository {
13 |
14 | override fun addTask(task: Task, result: (UiState>) -> Unit) {
15 | val reference = database.reference.child(FireDatabase.TASK).push()
16 | val uniqueKey = reference.key ?: "invalid"
17 | task.id = uniqueKey
18 | reference
19 | .setValue(task)
20 | .addOnSuccessListener {
21 | result.invoke(
22 | UiState.Success(Pair(task,"Task has been created successfully"))
23 | )
24 | }
25 | .addOnFailureListener {
26 | result.invoke(
27 | UiState.Failure(
28 | it.localizedMessage
29 | )
30 | )
31 | }
32 | }
33 |
34 | override fun updateTask(task: Task, result: (UiState>) -> Unit) {
35 | val reference = database.reference.child(FireDatabase.TASK).child(task.id)
36 | reference
37 | .setValue(task)
38 | .addOnSuccessListener {
39 | result.invoke(
40 | UiState.Success(Pair(task,"Task has been updated successfully"))
41 | )
42 | }
43 | .addOnFailureListener {
44 | result.invoke(
45 | UiState.Failure(
46 | it.localizedMessage
47 | )
48 | )
49 | }
50 | }
51 |
52 | override fun getTasks(user: User?, result: (UiState>) -> Unit) {
53 | val reference = database.reference.child(FireDatabase.TASK).orderByChild("user_id").equalTo(user?.id)
54 | reference.get()
55 | .addOnSuccessListener {
56 | val tasks = arrayListOf()
57 | for (item in it.children){
58 | val task = item.getValue(Task::class.java)
59 | tasks.add(task)
60 | }
61 | result.invoke(UiState.Success(tasks.filterNotNull()))
62 | }
63 | .addOnFailureListener {
64 | result.invoke(
65 | UiState.Failure(
66 | it.localizedMessage
67 | )
68 | )
69 | }
70 | }
71 |
72 | override fun deleteTask(task: Task, result: (UiState>) -> Unit) {
73 | val reference = database.reference.child(FireDatabase.TASK).child(task.id)
74 | reference.removeValue()
75 | .addOnSuccessListener {
76 | result.invoke(
77 | UiState.Success(Pair(task,"Task has been deleted successfully"))
78 | )
79 | }
80 | .addOnFailureListener {
81 | result.invoke(
82 | UiState.Failure(
83 | it.localizedMessage
84 | )
85 | )
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.di
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import com.example.firebasewithmvvm.util.SharedPrefConstants
6 | import com.google.gson.Gson
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.hilt.InstallIn
10 | import dagger.hilt.android.qualifiers.ApplicationContext
11 | import dagger.hilt.components.SingletonComponent
12 | import javax.inject.Singleton
13 |
14 | @InstallIn(SingletonComponent::class)
15 | @Module
16 | object AppModule {
17 |
18 | @Provides
19 | @Singleton
20 | fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences {
21 | return context.getSharedPreferences(SharedPrefConstants.LOCAL_SHARED_PREF,Context.MODE_PRIVATE)
22 | }
23 |
24 | @Provides
25 | @Singleton
26 | fun provideGson(): Gson {
27 | return Gson()
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/di/FirebaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.di
2 |
3 | import com.example.firebasewithmvvm.util.FirebaseStorageConstants
4 | import com.google.firebase.auth.FirebaseAuth
5 | import com.google.firebase.database.FirebaseDatabase
6 | import com.google.firebase.firestore.FirebaseFirestore
7 | import com.google.firebase.storage.FirebaseStorage
8 | import com.google.firebase.storage.StorageReference
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.InstallIn
12 | import dagger.hilt.components.SingletonComponent
13 | import javax.inject.Singleton
14 |
15 | @InstallIn(SingletonComponent::class)
16 | @Module
17 | object FirebaseModule {
18 |
19 | @Provides
20 | @Singleton
21 | fun provideFirebaseDatabaseInstance(): FirebaseDatabase{
22 | return FirebaseDatabase.getInstance()
23 | }
24 |
25 | @Provides
26 | @Singleton
27 | fun provideFireStoreInstance(): FirebaseFirestore{
28 | return FirebaseFirestore.getInstance()
29 | }
30 |
31 | @Provides
32 | @Singleton
33 | fun provideFirebaseAuthInstance(): FirebaseAuth{
34 | return FirebaseAuth.getInstance()
35 | }
36 |
37 | @Singleton
38 | @Provides
39 | fun provideFirebaseStroageInstance(): StorageReference {
40 | return FirebaseStorage.getInstance().getReference(FirebaseStorageConstants.ROOT_DIRECTORY)
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/di/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.di
2 |
3 | import android.content.SharedPreferences
4 | import com.example.firebasewithmvvm.data.repository.*
5 | import com.google.firebase.auth.FirebaseAuth
6 | import com.google.firebase.database.FirebaseDatabase
7 | import com.google.firebase.firestore.FirebaseFirestore
8 | import com.google.firebase.storage.StorageReference
9 | import com.google.gson.Gson
10 | import dagger.Module
11 | import dagger.Provides
12 | import dagger.hilt.InstallIn
13 | import dagger.hilt.components.SingletonComponent
14 | import javax.inject.Singleton
15 |
16 | @InstallIn(SingletonComponent::class)
17 | @Module
18 | object RepositoryModule {
19 |
20 | @Provides
21 | @Singleton
22 | fun provideNoteRepository(
23 | database: FirebaseFirestore,
24 | storageReference: StorageReference
25 | ): NoteRepository{
26 | return NoteRepositoryImp(database,storageReference)
27 | }
28 |
29 | @Provides
30 | @Singleton
31 | fun provideTaskRepository(
32 | database: FirebaseDatabase
33 | ): TaskRepository{
34 | return TaskRepositoryImp(database)
35 | }
36 |
37 | @Provides
38 | @Singleton
39 | fun provideAutghRepository(
40 | database: FirebaseFirestore,
41 | auth: FirebaseAuth,
42 | appPreferences: SharedPreferences,
43 | gson: Gson
44 | ): AuthRepository {
45 | return AuthRepositoryImp(auth,database,appPreferences,gson)
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/auth/AuthViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.auth
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import com.example.firebasewithmvvm.data.model.User
7 | import com.example.firebasewithmvvm.data.repository.AuthRepository
8 | import com.example.firebasewithmvvm.util.UiState
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class AuthViewModel @Inject constructor(
14 | val repository: AuthRepository
15 | ): ViewModel() {
16 |
17 | private val _register = MutableLiveData>()
18 | val register: LiveData>
19 | get() = _register
20 |
21 | private val _login = MutableLiveData>()
22 | val login: LiveData>
23 | get() = _login
24 |
25 | private val _forgotPassword = MutableLiveData>()
26 | val forgotPassword: LiveData>
27 | get() = _forgotPassword
28 |
29 |
30 | fun register(
31 | email: String,
32 | password: String,
33 | user: User
34 | ) {
35 | _register.value = UiState.Loading
36 | repository.registerUser(
37 | email = email,
38 | password = password,
39 | user = user
40 | ) { _register.value = it }
41 | }
42 |
43 | fun login(
44 | email: String,
45 | password: String
46 | ) {
47 | _login.value = UiState.Loading
48 | repository.loginUser(
49 | email,
50 | password
51 | ){
52 | _login.value = it
53 | }
54 | }
55 |
56 | fun forgotPassword(email: String) {
57 | _forgotPassword.value = UiState.Loading
58 | repository.forgotPassword(email){
59 | _forgotPassword.value = it
60 | }
61 | }
62 |
63 | fun logout(result: () -> Unit){
64 | repository.logout(result)
65 | }
66 |
67 | fun getSession(result: (User?) -> Unit){
68 | repository.getSession(result)
69 | }
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/auth/ForgotPasswordFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.auth
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.viewModels
9 | import androidx.navigation.fragment.findNavController
10 | import com.example.firebasewithmvvm.R
11 | import com.example.firebasewithmvvm.databinding.FragmentForgotPasswordBinding
12 | import com.example.firebasewithmvvm.databinding.FragmentLoginBinding
13 | import com.example.firebasewithmvvm.util.*
14 | import dagger.hilt.android.AndroidEntryPoint
15 |
16 |
17 | @AndroidEntryPoint
18 | class ForgotPasswordFragment : Fragment() {
19 |
20 | val TAG: String = "ForgotPasswordFragment"
21 | lateinit var binding: FragmentForgotPasswordBinding
22 | val viewModel: AuthViewModel by viewModels()
23 |
24 | override fun onCreateView(
25 | inflater: LayoutInflater, container: ViewGroup?,
26 | savedInstanceState: Bundle?
27 | ): View? {
28 | binding = FragmentForgotPasswordBinding.inflate(layoutInflater)
29 | return binding.root
30 | }
31 |
32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
33 | super.onViewCreated(view, savedInstanceState)
34 | observer()
35 | binding.forgotPassBtn.setOnClickListener {
36 | if (validation()){
37 | viewModel.forgotPassword(binding.emailEt.text.toString())
38 | }
39 | }
40 | }
41 |
42 | private fun observer(){
43 | viewModel.forgotPassword.observe(viewLifecycleOwner) { state ->
44 | when(state){
45 | is UiState.Loading -> {
46 | binding.forgotPassBtn.setText("")
47 | binding.forgotPassProgress.show()
48 | }
49 | is UiState.Failure -> {
50 | binding.forgotPassBtn.setText("Send")
51 | binding.forgotPassProgress.hide()
52 | toast(state.error)
53 | }
54 | is UiState.Success -> {
55 | binding.forgotPassBtn.setText("Send")
56 | binding.forgotPassProgress.hide()
57 | toast(state.data)
58 | }
59 | }
60 | }
61 | }
62 |
63 | fun validation(): Boolean {
64 | var isValid = true
65 |
66 | if (binding.emailEt.text.isNullOrEmpty()){
67 | isValid = false
68 | toast(getString(R.string.enter_email))
69 | }else{
70 | if (!binding.emailEt.text.toString().isValidEmail()){
71 | isValid = false
72 | toast(getString(R.string.invalid_email))
73 | }
74 | }
75 |
76 | return isValid
77 | }
78 |
79 |
80 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/auth/LoginFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.auth
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.util.Log
6 | import androidx.fragment.app.Fragment
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.fragment.app.viewModels
11 | import androidx.navigation.NavGraph
12 | import androidx.navigation.fragment.findNavController
13 | import com.example.firebasewithmvvm.R
14 | import com.example.firebasewithmvvm.databinding.FragmentLoginBinding
15 | import com.example.firebasewithmvvm.databinding.FragmentRegisterBinding
16 | import com.example.firebasewithmvvm.util.*
17 | import dagger.hilt.android.AndroidEntryPoint
18 |
19 | @AndroidEntryPoint
20 | class LoginFragment : Fragment() {
21 |
22 | val TAG: String = "RegisterFragment"
23 | lateinit var binding: FragmentLoginBinding
24 | val viewModel: AuthViewModel by viewModels()
25 |
26 | override fun onCreateView(
27 | inflater: LayoutInflater, container: ViewGroup?,
28 | savedInstanceState: Bundle?
29 | ): View? {
30 | binding = FragmentLoginBinding.inflate(layoutInflater)
31 | return binding.root
32 | }
33 |
34 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
35 | super.onViewCreated(view, savedInstanceState)
36 | observer()
37 | binding.loginBtn.setOnClickListener {
38 | if (validation()) {
39 | viewModel.login(
40 | email = binding.emailEt.text.toString(),
41 | password = binding.passEt.text.toString()
42 | )
43 | }
44 | }
45 |
46 | binding.forgotPassLabel.setOnClickListener {
47 | findNavController().navigate(R.id.action_loginFragment_to_forgotPasswordFragment)
48 | }
49 |
50 | binding.registerLabel.setOnClickListener {
51 | findNavController().navigate(R.id.action_loginFragment_to_registerFragment)
52 | }
53 | }
54 |
55 | fun observer(){
56 | viewModel.login.observe(viewLifecycleOwner) { state ->
57 | when(state){
58 | is UiState.Loading -> {
59 | binding.loginBtn.setText("")
60 | binding.loginProgress.show()
61 | }
62 | is UiState.Failure -> {
63 | binding.loginBtn.setText("Login")
64 | binding.loginProgress.hide()
65 | toast(state.error)
66 | }
67 | is UiState.Success -> {
68 | binding.loginBtn.setText("Login")
69 | binding.loginProgress.hide()
70 | toast(state.data)
71 | findNavController().navigate(R.id.action_loginFragment_to_home_navigation)
72 | }
73 | }
74 | }
75 | }
76 |
77 | fun validation(): Boolean {
78 | var isValid = true
79 |
80 | if (binding.emailEt.text.isNullOrEmpty()){
81 | isValid = false
82 | toast(getString(R.string.enter_email))
83 | }else{
84 | if (!binding.emailEt.text.toString().isValidEmail()){
85 | isValid = false
86 | toast(getString(R.string.invalid_email))
87 | }
88 | }
89 | if (binding.passEt.text.isNullOrEmpty()){
90 | isValid = false
91 | toast(getString(R.string.enter_password))
92 | }else{
93 | if (binding.passEt.text.toString().length < 8){
94 | isValid = false
95 | toast(getString(R.string.invalid_password))
96 | }
97 | }
98 | return isValid
99 | }
100 |
101 | override fun onStart() {
102 | super.onStart()
103 | viewModel.getSession { user ->
104 | if (user != null){
105 | findNavController().navigate(R.id.action_loginFragment_to_home_navigation)
106 | }
107 | }
108 | }
109 |
110 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/auth/RegisterFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.auth
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.viewModels
9 | import androidx.navigation.fragment.findNavController
10 | import com.example.firebasewithmvvm.R
11 | import com.example.firebasewithmvvm.data.model.User
12 | import com.example.firebasewithmvvm.databinding.FragmentRegisterBinding
13 | import com.example.firebasewithmvvm.util.*
14 | import dagger.hilt.android.AndroidEntryPoint
15 |
16 | @AndroidEntryPoint
17 | class RegisterFragment : Fragment() {
18 |
19 | val TAG: String = "RegisterFragment"
20 | lateinit var binding: FragmentRegisterBinding
21 | val viewModel: AuthViewModel by viewModels()
22 |
23 | override fun onCreateView(
24 | inflater: LayoutInflater, container: ViewGroup?,
25 | savedInstanceState: Bundle?
26 | ): View {
27 | binding = FragmentRegisterBinding.inflate(layoutInflater)
28 | return binding.root
29 | }
30 |
31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
32 | super.onViewCreated(view, savedInstanceState)
33 | observer()
34 | binding.registerBtn.setOnClickListener {
35 | if (validation()){
36 | viewModel.register(
37 | email = binding.emailEt.text.toString(),
38 | password = binding.passEt.text.toString(),
39 | user = getUserObj()
40 | )
41 | }
42 | }
43 | }
44 |
45 | fun observer() {
46 | viewModel.register.observe(viewLifecycleOwner) { state ->
47 | when(state){
48 | is UiState.Loading -> {
49 | binding.registerBtn.setText("")
50 | binding.registerProgress.show()
51 | }
52 | is UiState.Failure -> {
53 | binding.registerBtn.setText("Register")
54 | binding.registerProgress.hide()
55 | toast(state.error)
56 | }
57 | is UiState.Success -> {
58 | binding.registerBtn.setText("Register")
59 | binding.registerProgress.hide()
60 | toast(state.data)
61 | findNavController().navigate(R.id.action_registerFragment_to_home_navigation)
62 | }
63 | }
64 | }
65 | }
66 |
67 | fun getUserObj(): User {
68 | return User(
69 | id = "",
70 | first_name = binding.firstNameEt.text.toString(),
71 | last_name = binding.lastNameEt.text.toString(),
72 | job_title = binding.jobTitleEt.text.toString(),
73 | email = binding.emailEt.text.toString(),
74 | )
75 | }
76 |
77 | fun validation(): Boolean {
78 | var isValid = true
79 |
80 | if (binding.firstNameEt.text.isNullOrEmpty()){
81 | isValid = false
82 | toast(getString(R.string.enter_first_name))
83 | }
84 |
85 | if (binding.lastNameEt.text.isNullOrEmpty()){
86 | isValid = false
87 | toast(getString(R.string.enter_last_name))
88 | }
89 |
90 | if (binding.jobTitleEt.text.isNullOrEmpty()){
91 | isValid = false
92 | toast(getString(R.string.enter_job_title))
93 | }
94 |
95 | if (binding.emailEt.text.isNullOrEmpty()){
96 | isValid = false
97 | toast(getString(R.string.enter_email))
98 | }else{
99 | if (!binding.emailEt.text.toString().isValidEmail()){
100 | isValid = false
101 | toast(getString(R.string.invalid_email))
102 | }
103 | }
104 | if (binding.passEt.text.isNullOrEmpty()){
105 | isValid = false
106 | toast(getString(R.string.enter_password))
107 | }else{
108 | if (binding.passEt.text.toString().length < 8){
109 | isValid = false
110 | toast(getString(R.string.invalid_password))
111 | }
112 | }
113 | return isValid
114 | }
115 |
116 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/home/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.home
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.TextView
9 | import androidx.core.content.ContextCompat
10 | import androidx.fragment.app.viewModels
11 | import androidx.navigation.fragment.findNavController
12 | import androidx.viewpager2.widget.ViewPager2
13 | import com.example.firebasewithmvvm.R
14 | import com.example.firebasewithmvvm.databinding.FragmentHomeBinding
15 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel
16 | import com.example.firebasewithmvvm.util.HomeTabs
17 | import com.google.android.material.tabs.TabLayout
18 | import com.google.android.material.tabs.TabLayoutMediator
19 | import dagger.hilt.android.AndroidEntryPoint
20 |
21 | @AndroidEntryPoint
22 | class HomeFragment : Fragment() {
23 |
24 | val TAG: String = "HomeFragment"
25 | lateinit var binding: FragmentHomeBinding
26 | val authViewModel: AuthViewModel by viewModels()
27 |
28 | override fun onCreateView(
29 | inflater: LayoutInflater, container: ViewGroup?,
30 | savedInstanceState: Bundle?
31 | ): View {
32 | binding = FragmentHomeBinding.inflate(layoutInflater)
33 | return binding.root
34 | }
35 |
36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
37 | super.onViewCreated(view, savedInstanceState)
38 |
39 | binding.logout.setOnClickListener {
40 | authViewModel.logout {
41 | findNavController().navigate(R.id.action_homeFragment_to_loginFragment)
42 | }
43 | }
44 |
45 | binding.viewPager2.adapter = HomePagerAdapter(this)
46 | viewPager2SetupWithTabLayout(
47 | tabLayout = binding.tabLayout,
48 | viewPager2 = binding.viewPager2
49 | )
50 | binding.tabLayout.onTabSelectionListener()
51 | }
52 |
53 | private fun viewPager2SetupWithTabLayout(tabLayout: TabLayout, viewPager2: ViewPager2){
54 | TabLayoutMediator(tabLayout, viewPager2) { tab, position ->
55 | val view = LayoutInflater.from(requireContext()).inflate(R.layout.item_tab_layout, null)
56 | val textViewTitle: TextView = view.findViewById(R.id.tabTitle)
57 | when (position) {
58 | HomeTabs.NOTES.index -> {
59 | tab.customView = view
60 | textViewTitle.text = getString(R.string.notes)
61 | tab.onSelection(true)
62 | }
63 | HomeTabs.TASKS.index -> {
64 | tab.customView = view
65 | textViewTitle.text = getString(R.string.tasks)
66 | tab.onSelection(false)
67 | }
68 | }
69 | }.attach()
70 | }
71 |
72 | }
73 |
74 | inline fun TabLayout.onTabSelectionListener(
75 | crossinline onTabSelected: (TabLayout.Tab?) -> Unit = {},
76 | crossinline onTabUnselected: (TabLayout.Tab?) -> Unit? = {},
77 | ) {
78 | addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
79 | override fun onTabSelected(tab: TabLayout.Tab?) {
80 | tab?.onSelection(true)
81 | onTabSelected.invoke(tab)
82 | }
83 |
84 | override fun onTabUnselected(tab: TabLayout.Tab?) {
85 | tab?.onSelection(false)
86 | onTabUnselected.invoke(tab)
87 | }
88 |
89 | override fun onTabReselected(tab: TabLayout.Tab?) {}
90 | })
91 | }
92 |
93 | fun TabLayout.Tab.onSelection(isSelected: Boolean = true) {
94 | val textViewTitle: TextView? = customView?.findViewById(R.id.tabTitle)
95 | textViewTitle?.let { textView ->
96 | textView.setTextColor(
97 | ContextCompat.getColor(
98 | textView.context,
99 | if (isSelected) R.color.tab_selected else R.color.tab_unselected
100 | )
101 | )
102 | }
103 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/home/HomePagerAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.home
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.viewpager2.adapter.FragmentStateAdapter
5 | import com.example.firebasewithmvvm.ui.note.NoteListingFragment
6 | import com.example.firebasewithmvvm.ui.task.TaskListingFragment
7 | import com.example.firebasewithmvvm.util.HomeTabs
8 |
9 | class HomePagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
10 |
11 | override fun getItemCount(): Int = HomeTabs.values().size
12 |
13 | override fun createFragment(position: Int): Fragment {
14 | return when (position) {
15 | HomeTabs.NOTES.index -> NoteListingFragment.newInstance(HomeTabs.NOTES.name)
16 | HomeTabs.TASKS.index -> TaskListingFragment.newInstance(HomeTabs.TASKS.name)
17 | else -> throw IllegalStateException("Fragment not found")
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/note/ImageListingAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.note
2 |
3 | import android.net.Uri
4 | import android.view.LayoutInflater
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.example.firebasewithmvvm.databinding.ImageLayoutBinding
8 |
9 | class ImageListingAdapter(
10 | val onCancelClicked: ((Int, Uri) -> Unit)? = null,
11 | ) : RecyclerView.Adapter() {
12 |
13 | private var list: MutableList = arrayListOf()
14 |
15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
16 | val itemView = ImageLayoutBinding.inflate(LayoutInflater.from(parent.context),parent,false)
17 | return MyViewHolder(itemView)
18 | }
19 |
20 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
21 | val item = list[position]
22 | holder.bind(item,position)
23 | }
24 |
25 | fun updateList(list: MutableList){
26 | this.list = list
27 | notifyDataSetChanged()
28 | }
29 |
30 | fun removeItem(position: Int){
31 | list.removeAt(position)
32 | notifyItemChanged(position)
33 | }
34 |
35 | override fun getItemCount(): Int {
36 | return list.size
37 | }
38 |
39 | inner class MyViewHolder(val binding: ImageLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
40 | fun bind(item: Uri,position: Int) {
41 | binding.image.setImageURI(item)
42 | binding.cancel.setOnClickListener {
43 | onCancelClicked?.invoke(absoluteAdapterPosition, item)
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/note/NoteDetailFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.note
2 |
3 | import android.app.Activity
4 | import android.net.Uri
5 | import android.os.Bundle
6 | import android.util.Log
7 | import androidx.fragment.app.Fragment
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import android.widget.EditText
12 | import androidx.activity.result.ActivityResult
13 | import androidx.activity.result.contract.ActivityResultContracts
14 | import androidx.core.net.toUri
15 | import androidx.core.widget.doAfterTextChanged
16 | import androidx.fragment.app.viewModels
17 | import androidx.navigation.fragment.findNavController
18 | import androidx.recyclerview.widget.LinearLayoutManager
19 | import com.example.firebasewithmvvm.R
20 | import com.example.firebasewithmvvm.data.model.Note
21 | import com.example.firebasewithmvvm.databinding.FragmentNoteDetailBinding
22 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel
23 | import com.example.firebasewithmvvm.util.*
24 | import com.github.dhaval2404.imagepicker.ImagePicker
25 | import com.google.android.material.button.MaterialButton
26 | import dagger.hilt.android.AndroidEntryPoint
27 | import java.text.SimpleDateFormat
28 | import java.util.*
29 |
30 | @AndroidEntryPoint
31 | class NoteDetailFragment : Fragment() {
32 |
33 | val TAG: String = "NoteDetailFragment"
34 | lateinit var binding: FragmentNoteDetailBinding
35 | val viewModel: NoteViewModel by viewModels()
36 | val authViewModel: AuthViewModel by viewModels()
37 | var objNote: Note? = null
38 | var tagsList: MutableList = arrayListOf()
39 | var imageUris: MutableList = arrayListOf()
40 | val adapter by lazy {
41 | ImageListingAdapter(
42 | onCancelClicked = { pos, item -> onRemoveImage(pos,item)}
43 | )
44 | }
45 |
46 | private val startForProfileImageResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
47 | val resultCode = result.resultCode
48 | val data = result.data
49 | if (resultCode == Activity.RESULT_OK) {
50 | val fileUri = data?.data!!
51 | imageUris.add(fileUri)
52 | adapter.updateList(imageUris)
53 | binding.progressBar.hide()
54 | } else if (resultCode == ImagePicker.RESULT_ERROR) {
55 | binding.progressBar.hide()
56 | toast(ImagePicker.getError(data))
57 | } else {
58 | binding.progressBar.hide()
59 | Log.e(TAG,"Task Cancelled")
60 | }
61 | }
62 |
63 | override fun onCreateView(
64 | inflater: LayoutInflater, container: ViewGroup?,
65 | savedInstanceState: Bundle?
66 | ): View {
67 | if (this::binding.isInitialized){
68 | return binding.root
69 | }else {
70 | binding = FragmentNoteDetailBinding.inflate(layoutInflater)
71 | return binding.root
72 | }
73 | }
74 |
75 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
76 | super.onViewCreated(view, savedInstanceState)
77 | updateUI()
78 | observer()
79 | }
80 |
81 | private fun observer() {
82 | viewModel.addNote.observe(viewLifecycleOwner) { state ->
83 | when (state) {
84 | is UiState.Loading -> {
85 | binding.progressBar.show()
86 | }
87 | is UiState.Failure -> {
88 | binding.progressBar.hide()
89 | toast(state.error)
90 | }
91 | is UiState.Success -> {
92 | binding.progressBar.hide()
93 | toast(state.data.second)
94 | objNote = state.data.first
95 | isMakeEnableUI(false)
96 | binding.done.hide()
97 | binding.delete.show()
98 | binding.edit.show()
99 | }
100 | }
101 | }
102 | viewModel.updateNote.observe(viewLifecycleOwner) { state ->
103 | when (state) {
104 | is UiState.Loading -> {
105 | binding.progressBar.show()
106 | }
107 | is UiState.Failure -> {
108 | binding.progressBar.hide()
109 | toast(state.error)
110 | }
111 | is UiState.Success -> {
112 | binding.progressBar.hide()
113 | toast(state.data)
114 | binding.done.hide()
115 | binding.edit.show()
116 | isMakeEnableUI(false)
117 | }
118 | }
119 | }
120 |
121 | viewModel.deleteNote.observe(viewLifecycleOwner) { state ->
122 | when (state) {
123 | is UiState.Loading -> {
124 | binding.progressBar.show()
125 | }
126 | is UiState.Failure -> {
127 | binding.progressBar.hide()
128 | toast(state.error)
129 | }
130 | is UiState.Success -> {
131 | binding.progressBar.hide()
132 | toast(state.data)
133 | findNavController().navigateUp()
134 | }
135 | }
136 | }
137 | }
138 |
139 | private fun updateUI() {
140 | val sdf = SimpleDateFormat("dd MMM yyyy . hh:mm a")
141 | objNote = arguments?.getParcelable("note")
142 | binding.tags.layoutParams.height = 40.dpToPx
143 | objNote?.let { note ->
144 | binding.title.setText(note.title)
145 | binding.date.setText(sdf.format(note.date))
146 | tagsList = note.tags
147 | addTags(tagsList)
148 | binding.description.setText(note.description)
149 | binding.done.hide()
150 | binding.edit.show()
151 | binding.delete.show()
152 | isMakeEnableUI(false)
153 | } ?: run {
154 | binding.title.setText("")
155 | binding.date.setText(sdf.format(Date()))
156 | binding.description.setText("")
157 | binding.done.hide()
158 | binding.edit.hide()
159 | binding.delete.hide()
160 | isMakeEnableUI(true)
161 | }
162 | binding.images.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL,false)
163 | binding.images.adapter = adapter
164 | binding.images.itemAnimator = null
165 | imageUris = objNote?.images?.map { it.toUri() }?.toMutableList() ?: arrayListOf()
166 | adapter.updateList(imageUris)
167 | binding.addImageLl.setOnClickListener {
168 | binding.progressBar.show()
169 | ImagePicker.with(this)
170 | //.crop()
171 | .compress(1024)
172 | .galleryOnly()
173 | .createIntent { intent ->
174 | startForProfileImageResult.launch(intent)
175 | }
176 | }
177 | binding.back.setOnClickListener {
178 | findNavController().navigateUp()
179 | }
180 | binding.title.setOnClickListener {
181 | isMakeEnableUI(true)
182 | }
183 | binding.description.setOnClickListener {
184 | isMakeEnableUI(true)
185 | }
186 | binding.delete.setOnClickListener {
187 | objNote?.let { viewModel.deleteNote(it) }
188 | }
189 | binding.addTagLl.setOnClickListener {
190 | showAddTagDialog()
191 | }
192 | binding.edit.setOnClickListener {
193 | isMakeEnableUI(true)
194 | binding.done.show()
195 | binding.edit.hide()
196 | binding.title.requestFocus()
197 | }
198 | binding.done.setOnClickListener {
199 | if (validation()) {
200 | onDonePressed()
201 | }
202 | }
203 | binding.title.doAfterTextChanged {
204 | binding.done.show()
205 | binding.edit.hide()
206 | }
207 | binding.description.doAfterTextChanged {
208 | binding.done.show()
209 | binding.edit.hide()
210 | }
211 | }
212 |
213 | private fun onRemoveImage(pos: Int, item: Uri) {
214 | adapter.removeItem(pos)
215 | if (objNote != null){
216 | binding.edit.performClick()
217 | }
218 | }
219 |
220 | private fun showAddTagDialog(){
221 | val dialog = requireContext().createDialog(R.layout.add_tag_dialog, true)
222 | val button = dialog.findViewById(R.id.tag_dialog_add)
223 | val editText = dialog.findViewById(R.id.tag_dialog_et)
224 | button.setOnClickListener {
225 | if (editText.text.toString().isNullOrEmpty()) {
226 | toast(getString(R.string.error_tag_text))
227 | } else {
228 | val text = editText.text.toString()
229 | tagsList.add(text)
230 | binding.tags.apply {
231 | addChip(text, true) {
232 | tagsList.forEachIndexed { index, tag ->
233 | if (text.equals(tag)) {
234 | tagsList.removeAt(index)
235 | binding.tags.removeViewAt(index)
236 | }
237 | }
238 | if (tagsList.size == 0){
239 | layoutParams.height = 40.dpToPx
240 | }
241 | }
242 | layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
243 | }
244 | binding.done.show()
245 | binding.edit.hide()
246 | dialog.dismiss()
247 | }
248 | }
249 | dialog.show()
250 | }
251 |
252 | private fun addTags(note: MutableList) {
253 | if (note.size > 0) {
254 | binding.tags.apply {
255 | removeAllViews()
256 | note.forEachIndexed { index, tag ->
257 | addChip(tag, true) {
258 | if (isEnabled) {
259 | note.removeAt(index)
260 | this.removeViewAt(index)
261 | }
262 | }
263 | }
264 | }
265 | }
266 | }
267 |
268 | private fun isMakeEnableUI(isDisable: Boolean = false) {
269 | binding.title.isEnabled = isDisable
270 | binding.date.isEnabled = isDisable
271 | binding.tags.isEnabled = isDisable
272 | binding.addTagLl.isEnabled = isDisable
273 | binding.description.isEnabled = isDisable
274 | }
275 |
276 | private fun validation(): Boolean {
277 | var isValid = true
278 | if (binding.title.text.toString().isNullOrEmpty()) {
279 | isValid = false
280 | toast(getString(R.string.error_title))
281 | }
282 | if (binding.description.text.toString().isNullOrEmpty()) {
283 | isValid = false
284 | toast(getString(R.string.error_description))
285 | }
286 | return isValid
287 | }
288 |
289 | private fun getNote(): Note {
290 | return Note(
291 | id = objNote?.id ?: "",
292 | title = binding.title.text.toString(),
293 | description = binding.description.text.toString(),
294 | tags = tagsList,
295 | images = getImageUrls(),
296 | date = Date()
297 | ).apply { authViewModel.getSession { this.user_id = it?.id ?: "" } }
298 | }
299 |
300 | private fun getImageUrls(): List {
301 | if (imageUris.isNotEmpty()){
302 | return imageUris.map { it.toString() }
303 | }else{
304 | return objNote?.images ?: arrayListOf()
305 | }
306 | }
307 |
308 | private fun onDonePressed() {
309 | if (imageUris.isNotEmpty()){
310 | viewModel.onUploadMultipleFile(imageUris){ state ->
311 | when (state) {
312 | is UiState.Loading -> {
313 | binding.progressBar.show()
314 | }
315 | is UiState.Failure -> {
316 | binding.progressBar.hide()
317 | toast(state.error)
318 | }
319 | is UiState.Success -> {
320 | binding.progressBar.hide()
321 | if (objNote == null) {
322 | viewModel.addNote(getNote())
323 | } else {
324 | viewModel.updateNote(getNote())
325 | }
326 | }
327 | }
328 | }
329 | }else{
330 | if (objNote == null) {
331 | viewModel.addNote(getNote())
332 | } else {
333 | viewModel.updateNote(getNote())
334 | }
335 | }
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/note/NoteListingAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.note
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.example.firebasewithmvvm.data.model.Note
7 | import com.example.firebasewithmvvm.databinding.ItemNoteLayoutBinding
8 | import com.example.firebasewithmvvm.util.addChip
9 | import com.example.firebasewithmvvm.util.hide
10 | import java.text.SimpleDateFormat
11 |
12 | class NoteListingAdapter(
13 | val onItemClicked: (Int, Note) -> Unit
14 | ) : RecyclerView.Adapter() {
15 |
16 | val sdf = SimpleDateFormat("dd MMM yyyy")
17 | private var list: MutableList = arrayListOf()
18 |
19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
20 | val itemView = ItemNoteLayoutBinding.inflate(LayoutInflater.from(parent.context),parent,false)
21 | return MyViewHolder(itemView)
22 | }
23 |
24 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
25 | val item = list[position]
26 | holder.bind(item)
27 | }
28 |
29 | fun updateList(list: MutableList){
30 | this.list = list
31 | notifyDataSetChanged()
32 | }
33 |
34 | fun removeItem(position: Int){
35 | list.removeAt(position)
36 | notifyItemChanged(position)
37 | }
38 |
39 | override fun getItemCount(): Int {
40 | return list.size
41 | }
42 |
43 | inner class MyViewHolder(val binding: ItemNoteLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
44 | fun bind(item: Note){
45 | binding.title.setText(item.title)
46 | binding.date.setText(sdf.format(item.date))
47 | binding.tags.apply {
48 | if (item.tags.isNullOrEmpty()){
49 | hide()
50 | }else {
51 | removeAllViews()
52 | if (item.tags.size > 2) {
53 | item.tags.subList(0, 2).forEach { tag -> addChip(tag) }
54 | addChip("+${item.tags.size - 2}")
55 | } else {
56 | item.tags.forEach { tag -> addChip(tag) }
57 | }
58 | }
59 | }
60 | binding.desc.apply {
61 | if (item.description.length > 120){
62 | text = "${item.description.substring(0,120)}..."
63 | }else{
64 | text = item.description
65 | }
66 | }
67 | binding.itemLayout.setOnClickListener { onItemClicked.invoke(adapterPosition,item) }
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/note/NoteListingFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.note
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.fragment.app.Fragment
10 | import androidx.fragment.app.viewModels
11 | import androidx.navigation.NavGraph
12 | import androidx.navigation.findNavController
13 | import androidx.navigation.fragment.findNavController
14 | import androidx.recyclerview.widget.LinearLayoutManager
15 | import androidx.recyclerview.widget.StaggeredGridLayoutManager
16 | import com.example.firebasewithmvvm.R
17 | import com.example.firebasewithmvvm.databinding.FragmentNoteListingBinding
18 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel
19 | import com.example.firebasewithmvvm.util.UiState
20 | import com.example.firebasewithmvvm.util.hide
21 | import com.example.firebasewithmvvm.util.show
22 | import com.example.firebasewithmvvm.util.toast
23 | import dagger.hilt.android.AndroidEntryPoint
24 |
25 |
26 | private const val ARG_PARAM1 = "param1"
27 |
28 | @AndroidEntryPoint
29 | class NoteListingFragment : Fragment() {
30 |
31 | val TAG: String = "NoteListingFragment"
32 | var param1: String? = null
33 | lateinit var binding: FragmentNoteListingBinding
34 | val viewModel: NoteViewModel by viewModels()
35 | val authViewModel: AuthViewModel by viewModels()
36 | val adapter by lazy {
37 | NoteListingAdapter(
38 | onItemClicked = { pos, item ->
39 | findNavController().navigate(R.id.action_noteListingFragment_to_noteDetailFragment,Bundle().apply {
40 | putParcelable("note",item)
41 | })
42 | }
43 | )
44 | }
45 |
46 | override fun onCreate(savedInstanceState: Bundle?) {
47 | super.onCreate(savedInstanceState)
48 | arguments?.let {
49 | param1 = it.getString(ARG_PARAM1)
50 | }
51 | }
52 |
53 | override fun onCreateView(
54 | inflater: LayoutInflater, container: ViewGroup?,
55 | savedInstanceState: Bundle?
56 | ): View {
57 | if (this::binding.isInitialized){
58 | return binding.root
59 | }else {
60 | binding = FragmentNoteListingBinding.inflate(layoutInflater)
61 | return binding.root
62 | }
63 | }
64 |
65 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
66 | super.onViewCreated(view, savedInstanceState)
67 | oberver()
68 | val staggeredGridLayoutManager = StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL)
69 | binding.recyclerView.layoutManager = staggeredGridLayoutManager
70 | binding.recyclerView.adapter = adapter
71 | binding.button.setOnClickListener {
72 | findNavController().navigate(R.id.action_noteListingFragment_to_noteDetailFragment)
73 | }
74 |
75 | authViewModel.getSession {
76 | viewModel.getNotes(it)
77 | }
78 | }
79 |
80 | private fun oberver(){
81 | viewModel.note.observe(viewLifecycleOwner) { state ->
82 | when(state){
83 | is UiState.Loading -> {
84 | binding.progressBar.show()
85 | }
86 | is UiState.Failure -> {
87 | binding.progressBar.hide()
88 | toast(state.error)
89 | }
90 | is UiState.Success -> {
91 | binding.progressBar.hide()
92 | adapter.updateList(state.data.toMutableList())
93 | }
94 | }
95 | }
96 | }
97 |
98 | companion object {
99 | @JvmStatic
100 | fun newInstance(param1: String) =
101 | NoteListingFragment().apply {
102 | arguments = Bundle().apply {
103 | putString(ARG_PARAM1, param1)
104 | }
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/note/NoteViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.note
2 |
3 | import android.net.Uri
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import com.example.firebasewithmvvm.data.model.Note
9 | import com.example.firebasewithmvvm.data.model.User
10 | import com.example.firebasewithmvvm.data.repository.NoteRepository
11 | import com.example.firebasewithmvvm.util.UiState
12 | import dagger.hilt.android.lifecycle.HiltViewModel
13 | import kotlinx.coroutines.launch
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class NoteViewModel @Inject constructor(
18 | val repository: NoteRepository
19 | ): ViewModel() {
20 |
21 | private val _notes = MutableLiveData>>()
22 | val note: LiveData>>
23 | get() = _notes
24 |
25 | private val _addNote = MutableLiveData>>()
26 | val addNote: LiveData>>
27 | get() = _addNote
28 |
29 | private val _updateNote = MutableLiveData>()
30 | val updateNote: LiveData>
31 | get() = _updateNote
32 |
33 | private val _deleteNote = MutableLiveData>()
34 | val deleteNote: LiveData>
35 | get() = _deleteNote
36 |
37 | fun getNotes(user: User?) {
38 | _notes.value = UiState.Loading
39 | repository.getNotes(user) { _notes.value = it }
40 | }
41 |
42 | fun addNote(note: Note){
43 | _addNote.value = UiState.Loading
44 | repository.addNote(note) { _addNote.value = it }
45 | }
46 |
47 | fun updateNote(note: Note){
48 | _updateNote.value = UiState.Loading
49 | repository.updateNote(note) { _updateNote.value = it }
50 | }
51 |
52 | fun deleteNote(note: Note){
53 | _deleteNote.value = UiState.Loading
54 | repository.deleteNote(note) { _deleteNote.value = it }
55 | }
56 |
57 | fun onUploadSingleFile(fileUris: Uri, onResult: (UiState) -> Unit){
58 | onResult.invoke(UiState.Loading)
59 | viewModelScope.launch {
60 | repository.uploadSingleFile(fileUris,onResult)
61 | }
62 | }
63 |
64 | fun onUploadMultipleFile(fileUris: List, onResult: (UiState>) -> Unit){
65 | onResult.invoke(UiState.Loading)
66 | viewModelScope.launch {
67 | repository.uploadMultipleFile(fileUris,onResult)
68 | }
69 | }
70 |
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/task/CreateTaskFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.task
2 |
3 | import android.app.Dialog
4 | import android.content.DialogInterface
5 | import android.graphics.Color
6 | import android.os.Bundle
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.fragment.app.viewModels
11 | import com.example.firebasewithmvvm.R
12 | import com.example.firebasewithmvvm.data.model.Task
13 | import com.example.firebasewithmvvm.databinding.FragmentCreateTaskBinding
14 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel
15 | import com.example.firebasewithmvvm.util.UiState
16 | import com.example.firebasewithmvvm.util.hide
17 | import com.example.firebasewithmvvm.util.show
18 | import com.example.firebasewithmvvm.util.toast
19 | import com.google.android.material.bottomsheet.BottomSheetDialog
20 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment
21 | import dagger.hilt.android.AndroidEntryPoint
22 | import java.text.SimpleDateFormat
23 | import java.util.*
24 |
25 | @AndroidEntryPoint
26 | class CreateTaskFragment(private val task: Task? = null) : BottomSheetDialogFragment() {
27 |
28 | val TAG: String = "CreateTaskFragment"
29 | lateinit var binding: FragmentCreateTaskBinding
30 | val viewModel: TaskViewModel by viewModels()
31 | val authViewModel: AuthViewModel by viewModels()
32 | var closeFunction: ((Boolean) -> Unit)? = null
33 | var isSuccessAddTask: Boolean = false
34 |
35 |
36 | override fun onCreateView(
37 | inflater: LayoutInflater, container: ViewGroup?,
38 | savedInstanceState: Bundle?
39 | ): View {
40 | binding = FragmentCreateTaskBinding.inflate(layoutInflater)
41 | return binding.root
42 | }
43 |
44 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
45 | super.onViewCreated(view, savedInstanceState)
46 |
47 | task?.let {
48 | binding.taskEt.setText(it.description)
49 | }
50 |
51 | binding.cancel.setOnClickListener {
52 | this.dismiss()
53 | }
54 |
55 | binding.done.setOnClickListener {
56 | if (validation()) {
57 | if (task == null) {
58 | viewModel.addTask(getTask())
59 | }else{
60 | task.description = binding.taskEt.text.toString()
61 | viewModel.updateTask(task)
62 | }
63 | }
64 | }
65 | observer()
66 | }
67 |
68 | private fun observer(){
69 | viewModel.addTask.observe(viewLifecycleOwner) { state ->
70 | when(state){
71 | is UiState.Loading -> {
72 | binding.progressBar.show()
73 | }
74 | is UiState.Failure -> {
75 | binding.progressBar.hide()
76 | toast(state.error)
77 | }
78 | is UiState.Success -> {
79 | isSuccessAddTask = true
80 | binding.progressBar.hide()
81 | toast(state.data.second)
82 | this.dismiss()
83 | }
84 | }
85 | }
86 |
87 | viewModel.updateTask.observe(viewLifecycleOwner) { state ->
88 | when(state){
89 | is UiState.Loading -> {
90 | binding.progressBar.show()
91 | }
92 | is UiState.Failure -> {
93 | binding.progressBar.hide()
94 | toast(state.error)
95 | }
96 | is UiState.Success -> {
97 | isSuccessAddTask = true
98 | binding.progressBar.hide()
99 | toast(state.data.second)
100 | this.dismiss()
101 | }
102 | }
103 | }
104 | }
105 |
106 | private fun validation(): Boolean {
107 | var isValid = true
108 | if (binding.taskEt.text.toString().isNullOrEmpty()) {
109 | isValid = false
110 | toast(getString(R.string.error_task_detail))
111 | }
112 | return isValid
113 | }
114 |
115 | private fun getTask(): Task {
116 | val sdf = SimpleDateFormat("dd MMM yyyy . hh:mm a")
117 | return Task(
118 | id = "",
119 | description = binding.taskEt.text.toString(),
120 | date = sdf.format(Date())
121 | ).apply { authViewModel.getSession { this.user_id = it?.id ?: "" } }
122 | }
123 |
124 | fun setDismissListener(function: ((Boolean) -> Unit)?) {
125 | closeFunction = function
126 | }
127 |
128 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
129 | val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
130 | dialog.setOnShowListener { setupBottomSheet(it) }
131 | return dialog
132 | }
133 |
134 | private fun setupBottomSheet(dialogInterface: DialogInterface) {
135 | val bottomSheetDialog = dialogInterface as BottomSheetDialog
136 | val bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet) ?: return
137 | bottomSheet.setBackgroundColor(Color.TRANSPARENT)
138 | }
139 |
140 | override fun onDismiss(dialog: DialogInterface) {
141 | super.onDismiss(dialog)
142 | closeFunction?.invoke(isSuccessAddTask)
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/task/TaskListingAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.task
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.example.firebasewithmvvm.data.model.Task
7 | import com.example.firebasewithmvvm.databinding.TaskLayoutBinding
8 |
9 | class TaskListingAdapter(
10 | val onItemClicked: ((Int, Task) -> Unit)? = null,
11 | val onDeleteClicked: ((Int, Task) -> Unit)? = null,
12 | ) : RecyclerView.Adapter() {
13 |
14 | private var list: MutableList = arrayListOf()
15 |
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
17 | val itemView = TaskLayoutBinding.inflate(LayoutInflater.from(parent.context),parent,false)
18 | return MyViewHolder(itemView)
19 | }
20 |
21 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
22 | val item = list[position]
23 | holder.bind(item,position)
24 | }
25 |
26 | fun updateList(list: MutableList){
27 | this.list = list
28 | notifyDataSetChanged()
29 | }
30 |
31 | fun removeItem(position: Int){
32 | list.removeAt(position)
33 | notifyItemChanged(position)
34 | }
35 |
36 | override fun getItemCount(): Int {
37 | return list.size
38 | }
39 |
40 | inner class MyViewHolder(val binding: TaskLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
41 | fun bind(item: Task,position: Int) {
42 | binding.title.setText(item.description)
43 | binding.itemLayout.setOnClickListener {
44 | onItemClicked?.invoke(position,item)
45 | }
46 | binding.delete.setOnClickListener {
47 | onDeleteClicked?.invoke(position,item)
48 | }
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/task/TaskListingFragment.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.task
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.viewModels
9 | import androidx.recyclerview.widget.LinearLayoutManager
10 | import com.example.firebasewithmvvm.data.model.Task
11 | import com.example.firebasewithmvvm.databinding.FragmentTaskListingBinding
12 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel
13 | import com.example.firebasewithmvvm.util.UiState
14 | import com.example.firebasewithmvvm.util.hide
15 | import com.example.firebasewithmvvm.util.show
16 | import com.example.firebasewithmvvm.util.toast
17 | import dagger.hilt.android.AndroidEntryPoint
18 |
19 | private const val ARG_PARAM1 = "param1"
20 |
21 | @AndroidEntryPoint
22 | class TaskListingFragment : Fragment() {
23 |
24 | val TAG: String = "TaskListingFragment"
25 | private var param1: String? = null
26 | val viewModel: TaskViewModel by viewModels()
27 | val authViewModel: AuthViewModel by viewModels()
28 | lateinit var binding: FragmentTaskListingBinding
29 | var deleteItemPos = -1
30 | val adapter by lazy{
31 | TaskListingAdapter(
32 | onItemClicked = { pos, item -> onTaskClicked(item)},
33 | onDeleteClicked = { pos, item -> onDeleteClicked(pos,item) }
34 | )
35 | }
36 |
37 | override fun onCreate(savedInstanceState: Bundle?) {
38 | super.onCreate(savedInstanceState)
39 | arguments?.let {
40 | param1 = it.getString(ARG_PARAM1)
41 | }
42 | }
43 |
44 | override fun onCreateView(
45 | inflater: LayoutInflater, container: ViewGroup?,
46 | savedInstanceState: Bundle?
47 | ): View {
48 | if (this::binding.isInitialized){
49 | return binding.root
50 | }else {
51 | binding = FragmentTaskListingBinding.inflate(layoutInflater)
52 | return binding.root
53 | }
54 | }
55 |
56 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
57 | super.onViewCreated(view, savedInstanceState)
58 | binding.addTaskButton.setOnClickListener {
59 | val createTaskFragmentSheet = CreateTaskFragment()
60 | createTaskFragmentSheet.setDismissListener {
61 | if (it) {
62 | authViewModel.getSession {
63 | viewModel.getTasks(it)
64 | }
65 | }
66 | }
67 | createTaskFragmentSheet.show(childFragmentManager,"create_task")
68 | }
69 |
70 | binding.taskListing.layoutManager = LinearLayoutManager(requireContext())
71 | binding.taskListing.adapter = adapter
72 |
73 | authViewModel.getSession {
74 | viewModel.getTasks(it)
75 | }
76 | observer()
77 | }
78 |
79 | private fun observer(){
80 | viewModel.tasks.observe(viewLifecycleOwner) { state ->
81 | when(state){
82 | is UiState.Loading -> {
83 | binding.progressBar.show()
84 | }
85 | is UiState.Failure -> {
86 | binding.progressBar.hide()
87 | toast(state.error)
88 | }
89 | is UiState.Success -> {
90 | binding.progressBar.hide()
91 | adapter.updateList(state.data.toMutableList())
92 | }
93 | }
94 | }
95 | viewModel.deleteTask.observe(viewLifecycleOwner) { state ->
96 | when(state){
97 | is UiState.Loading -> {
98 | binding.progressBar.show()
99 | }
100 | is UiState.Failure -> {
101 | binding.progressBar.hide()
102 | toast(state.error)
103 | }
104 | is UiState.Success -> {
105 | binding.progressBar.hide()
106 | toast(state.data.second)
107 | adapter.removeItem(deleteItemPos)
108 | }
109 | }
110 | }
111 | }
112 |
113 | private fun onTaskClicked(task: Task){
114 | val createTaskFragmentSheet = CreateTaskFragment(task)
115 | createTaskFragmentSheet.setDismissListener {
116 | if (it) {
117 | authViewModel.getSession {
118 | viewModel.getTasks(it)
119 | }
120 | }
121 | }
122 | createTaskFragmentSheet.show(childFragmentManager,"create_task")
123 | }
124 |
125 | private fun onDeleteClicked(pos: Int, item: Task) {
126 | deleteItemPos = pos
127 | viewModel.deleteTask(item)
128 | }
129 |
130 | companion object {
131 | @JvmStatic
132 | fun newInstance(param1: String) =
133 | TaskListingFragment().apply {
134 | arguments = Bundle().apply {
135 | putString(ARG_PARAM1, param1)
136 | }
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/ui/task/TaskViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.ui.task
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import com.example.firebasewithmvvm.data.model.Task
7 | import com.example.firebasewithmvvm.data.model.User
8 | import com.example.firebasewithmvvm.data.repository.TaskRepository
9 | import com.example.firebasewithmvvm.util.UiState
10 | import com.google.android.gms.tasks.Tasks
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class TaskViewModel @Inject constructor(
16 | val repository: TaskRepository
17 | ): ViewModel() {
18 |
19 | private val _addTask = MutableLiveData>>()
20 | val addTask: LiveData>>
21 | get() = _addTask
22 |
23 | private val _updateTask = MutableLiveData>>()
24 | val updateTask: LiveData>>
25 | get() = _updateTask
26 |
27 | private val _deleteTask = MutableLiveData>>()
28 | val deleteTask: LiveData>>
29 | get() = _deleteTask
30 |
31 |
32 | private val _tasks = MutableLiveData>>()
33 | val tasks: LiveData>>
34 | get() = _tasks
35 |
36 | fun addTask(task: Task){
37 | _addTask.value = UiState.Loading
38 | repository.addTask(task) { _addTask.value = it }
39 | }
40 |
41 | fun updateTask(task: Task){
42 | _updateTask.value = UiState.Loading
43 | repository.updateTask(task) { _updateTask.value = it }
44 | }
45 |
46 | fun getTasks(user: User?) {
47 | _tasks.value = UiState.Loading
48 | repository.getTasks(user) { _tasks.value = it }
49 | }
50 |
51 | fun deleteTask(task: Task){
52 | _deleteTask.value = UiState.Loading
53 | repository.deleteTask(task) { _deleteTask.value = it }
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/util/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.util
2 |
3 | object FireStoreCollection{
4 | val NOTE = "note"
5 | val USER = "user"
6 | }
7 |
8 | object FireDatabase{
9 | val TASK = "task"
10 | }
11 |
12 | object FireStoreDocumentField {
13 | val DATE = "date"
14 | val USER_ID = "user_id"
15 | }
16 |
17 | object SharedPrefConstants {
18 | val LOCAL_SHARED_PREF = "local_shared_pref"
19 | val USER_SESSION = "user_session"
20 | }
21 |
22 | object FirebaseStorageConstants {
23 | val ROOT_DIRECTORY = "app"
24 | val NOTE_IMAGES = "note"
25 | }
26 |
27 | enum class HomeTabs(val index: Int, val key: String) {
28 | NOTES(0, "notes"),
29 | TASKS(1, "tasks"),
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/util/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.util
2 |
3 | import android.app.Dialog
4 | import android.content.Context
5 | import android.content.res.Resources
6 | import android.graphics.Color
7 | import android.graphics.drawable.ColorDrawable
8 | import android.view.*
9 | import android.widget.Toast
10 | import androidx.core.content.ContextCompat
11 | import androidx.fragment.app.Fragment
12 | import com.example.firebasewithmvvm.R
13 | import com.google.android.material.chip.Chip
14 | import com.google.android.material.chip.ChipGroup
15 |
16 |
17 | fun View.hide(){
18 | visibility = View.GONE
19 | }
20 |
21 | fun View.show(){
22 | visibility = View.VISIBLE
23 | }
24 |
25 | fun View.disable(){
26 | isEnabled = false
27 | }
28 |
29 | fun View.enabled(){
30 | isEnabled = true
31 | }
32 |
33 | fun Fragment.toast(msg: String?){
34 | Toast.makeText(requireContext(),msg,Toast.LENGTH_LONG).show()
35 | }
36 |
37 | fun ChipGroup.addChip(
38 | text: String,
39 | isTouchTargeSize: Boolean = false,
40 | closeIconListener: View.OnClickListener? = null
41 | ) {
42 | val chip: Chip = LayoutInflater.from(context).inflate(R.layout.item_chip,null,false) as Chip
43 | chip.text = if (text.length > 9) text.substring(0,9) + "..." else text
44 | chip.isClickable = false
45 | chip.setEnsureMinTouchTargetSize(isTouchTargeSize)
46 | if (closeIconListener != null){
47 | chip.closeIcon = ContextCompat.getDrawable(context, com.google.android.material.R.drawable.ic_mtrl_chip_close_circle)
48 | chip.isCloseIconVisible = true
49 | chip.setOnCloseIconClickListener(closeIconListener)
50 | }
51 | addView(chip)
52 | }
53 |
54 | fun Context.createDialog(layout: Int, cancelable: Boolean): Dialog {
55 | val dialog = Dialog(this, android.R.style.Theme_Dialog)
56 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
57 | dialog.setContentView(layout)
58 | dialog.window?.setGravity(Gravity.CENTER)
59 | dialog.window?.setLayout(
60 | WindowManager.LayoutParams.MATCH_PARENT,
61 | WindowManager.LayoutParams.WRAP_CONTENT
62 | )
63 | dialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
64 | dialog.setCancelable(cancelable)
65 | return dialog
66 | }
67 |
68 | val Int.dpToPx: Int
69 | get() = (this * Resources.getSystem().displayMetrics.density).toInt()
70 |
71 | val Int.pxToDp: Int
72 | get() = (this / Resources.getSystem().displayMetrics.density).toInt()
73 |
74 | fun String.isValidEmail() =
75 | isNotEmpty() && android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/firebasewithmvvm/util/UiState.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm.util
2 |
3 | sealed class UiState {
4 | object Loading: UiState()
5 | data class Success(val data: T): UiState()
6 | data class Failure(val error: String?): UiState()
7 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/black_rect_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/black_white_rect_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_add_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_add_circle_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_arrow_back_ios_24.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_cancel_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_check_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_delete_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_edit_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_exit_to_app_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/font/titillium_web_black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/font/titillium_web_black.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/titillium_web_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/font/titillium_web_bold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/titillium_web_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/font/titillium_web_light.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/titillium_web_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/font/titillium_web_regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/add_tag_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
29 |
30 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_create_task.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
27 |
28 |
38 |
39 |
51 |
52 |
62 |
63 |
64 |
65 |
81 |
82 |
92 |
93 |
99 |
100 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_forgot_password.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
33 |
34 |
46 |
47 |
66 |
67 |
76 |
77 |
87 |
88 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
28 |
29 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
33 |
34 |
46 |
47 |
66 |
67 |
80 |
81 |
95 |
96 |
108 |
109 |
123 |
124 |
133 |
134 |
144 |
145 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_note_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
31 |
32 |
43 |
44 |
55 |
56 |
66 |
67 |
75 |
76 |
86 |
87 |
92 |
93 |
105 |
106 |
116 |
117 |
127 |
128 |
136 |
137 |
146 |
147 |
156 |
157 |
158 |
159 |
169 |
170 |
178 |
179 |
188 |
189 |
198 |
199 |
200 |
201 |
210 |
211 |
219 |
220 |
232 |
233 |
234 |
235 |
236 |
237 |
247 |
248 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_note_listing.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
31 |
32 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_register.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
20 |
21 |
32 |
33 |
45 |
46 |
65 |
66 |
78 |
79 |
98 |
99 |
111 |
112 |
131 |
132 |
144 |
145 |
164 |
165 |
178 |
179 |
193 |
194 |
195 |
204 |
205 |
215 |
216 |
226 |
227 |
228 |
229 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_task_listing.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/image_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
21 |
22 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_chip.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_note_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
18 |
19 |
28 |
29 |
39 |
40 |
48 |
49 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_tab_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/task_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
19 |
20 |
31 |
32 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/navigation/app_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
16 |
17 |
18 |
23 |
26 |
29 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/home_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
18 |
19 |
20 |
23 |
24 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 |
9 | #FFFFFFFF
10 |
11 | #FF000000
12 | #B3000000
13 | #80000000
14 | #4D000000
15 | #1A000000
16 |
17 | #FAFBFF
18 |
19 | #FF000000
20 | #4D000000
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Note Taking
3 |
4 | Hello blank fragment
5 | In this chapter, you’re going to learn about the different types of subjects in RxJava, see how to work with each one and why you might choose one over another based on some common use cases.
6 | You’ve gotten a handle on what an Observable is, how to create one, how to subscribe to it, and how to dispose of things when you’re done. Observables are a fundamental part of RxJava, but a common need when developing apps is to manually add new values onto an Observable at runtime that will then be emitted to subscribers. What you want is something that can act as both an Observable and as an observer. And that something is called a subject.
7 | Description missing
8 | Title missing
9 | Enter text
10 | Enter first name
11 | Enter last name
12 | Enter job title
13 | Enter email address
14 | Invalid email entered
15 | Enter password
16 | Your password must be at least 8 characters
17 | Logout
18 | Notes
19 | Tasks
20 | Notes
21 | Tasks
22 | Home
23 | Create Task
24 | Create
25 | Done
26 | Task detail is missing
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
20 |
21 |
24 |
25 |
28 |
29 |
32 |
33 |
37 |
38 |
42 |
43 |
49 |
50 |
54 |
55 |
59 |
60 |
63 |
64 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/firebasewithmvvm/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.firebasewithmvvm
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | dependencies {
4 | classpath 'com.google.gms:google-services:4.3.3'
5 | classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.5'
6 | }
7 | }
8 |
9 | plugins {
10 | id 'com.android.application' version '7.1.0' apply false
11 | id 'com.android.library' version '7.1.0' apply false
12 | id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
13 | }
14 |
15 | task clean(type: Delete) {
16 | delete rootProject.buildDir
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat May 28 11:54:50 PKT 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/screenshots/note-taking-detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-detail.png
--------------------------------------------------------------------------------
/screenshots/note-taking-forgotpassword.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-forgotpassword.png
--------------------------------------------------------------------------------
/screenshots/note-taking-listing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-listing.png
--------------------------------------------------------------------------------
/screenshots/note-taking-login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-login.png
--------------------------------------------------------------------------------
/screenshots/note-taking-register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-register.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | maven { url "https://jitpack.io" }
7 | }
8 | }
9 | dependencyResolutionManagement {
10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
11 | repositories {
12 | google()
13 | mavenCentral()
14 | maven { url "https://jitpack.io" }
15 | }
16 | }
17 | rootProject.name = "FirebaseWithMVVM"
18 | include ':app'
19 |
--------------------------------------------------------------------------------