├── app
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── wiseassblog
│ │ └── jetpacknotesmvvmkotlin
│ │ ├── common
│ │ ├── AndroidExtensions.kt
│ │ ├── BaseViewModel.kt
│ │ ├── Constants.kt
│ │ ├── DataExtensions.kt
│ │ └── Result.kt
│ │ ├── login
│ │ ├── LoginActivity.kt
│ │ ├── LoginEvent.kt
│ │ ├── LoginView.kt
│ │ ├── UserViewModel.kt
│ │ └── buildlogic
│ │ │ ├── LoginInjector.kt
│ │ │ └── UserViewModelFactory.kt
│ │ ├── model
│ │ ├── FirebaseNote.kt
│ │ ├── LoginResult.kt
│ │ ├── Note.kt
│ │ ├── NoteDao.kt
│ │ ├── RoomNote.kt
│ │ ├── RoomNoteDatabase.kt
│ │ ├── User.kt
│ │ ├── implementations
│ │ │ ├── FirebaseUserRepoImpl.kt
│ │ │ └── NoteRepoImpl.kt
│ │ └── repository
│ │ │ ├── INoteRepository.kt
│ │ │ └── IUserRepository.kt
│ │ └── note
│ │ ├── NoteActivity.kt
│ │ ├── NoteListViewModel.kt
│ │ ├── NoteListViewModelFactory.kt
│ │ ├── NoteViewModel.kt
│ │ ├── NoteViewModelFactory.kt
│ │ ├── notedetail
│ │ ├── NoteDetailEvent.kt
│ │ ├── NoteDetailView.kt
│ │ └── buildlogic
│ │ │ ├── NoteDetailInjector.kt
│ │ │ └── NoteViewModelFactory.kt
│ │ └── notelist
│ │ ├── NoteDiffUtilCallback.kt
│ │ ├── NoteListAdapter.kt
│ │ ├── NoteListEvent.kt
│ │ ├── NoteListView.kt
│ │ └── buildlogic
│ │ ├── NoteListInjector.kt
│ │ └── NoteListViewModelFactory.kt
│ └── res
│ ├── drawable-land
│ ├── space_bg_one.png
│ ├── space_bg_three.png
│ └── space_bg_two.png
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── antenna_loop.xml
│ ├── antenna_loop_fast.xml
│ ├── ic_access_time_black_24dp.xml
│ ├── ic_antenna_empty.xml
│ ├── ic_antenna_full.xml
│ ├── ic_antenna_half.xml
│ ├── ic_arrow_back_black_24dp.xml
│ ├── ic_baseline_add_24px.xml
│ ├── ic_baseline_event_24px.xml
│ ├── ic_delete_forever_black_24dp.xml
│ ├── ic_done_black_24dp.xml
│ ├── ic_launcher_background.xml
│ ├── ic_visibility_black_24dp.xml
│ ├── ic_visibility_off_black_24dp.xml
│ ├── ic_vpn_key_black_24dp.xml
│ ├── im_rocket_one.xml
│ ├── im_rocket_three.xml
│ ├── im_rocket_two.xml
│ ├── rocket_loop.xml
│ ├── rocket_one.png
│ ├── space_bg_one.png
│ ├── space_bg_three.png
│ ├── space_bg_two.png
│ └── space_loop.xml
│ ├── layout
│ ├── activity_login.xml
│ ├── activity_note.xml
│ ├── fragment_login.xml
│ ├── fragment_note_detail.xml
│ ├── fragment_note_list.xml
│ └── item_note.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── navigation
│ └── nav_graph.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── view_styles.xml
├── build.gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'kotlin-kapt'
5 | apply plugin: 'io.fabric'
6 | apply plugin: 'androidx.navigation.safeargs.kotlin'
7 |
8 |
9 | android {
10 | compileSdkVersion rootProject.compileSdkVersion
11 | defaultConfig {
12 | applicationId "com.wiseassblog.jetpacknotesmvvmkotlin"
13 | minSdkVersion rootProject.minSdkVersion
14 | targetSdkVersion rootProject.targetSdkVersion
15 | versionCode 1
16 | versionName "1.0"
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables.useSupportLibrary = true
19 | }
20 | buildTypes {
21 | debug {
22 | minifyEnabled false
23 | }
24 | }
25 |
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | }
31 |
32 | dependencies {
33 |
34 | implementation "androidx.appcompat:appcompat:$rootProject.supportLibraryVersion"
35 | implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion"
36 | implementation "androidx.recyclerview:recyclerview:$rootProject.recyclerViewVersion"
37 | implementation "com.google.android.material:material:$rootProject.materialVersion"
38 |
39 |
40 | implementation "androidx.core:core-ktx:$rootProject.ktxVersion"
41 | implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.lifecycleVersion"
42 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.lifecycleVersion"
43 | implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.navigationVersion"
44 | implementation "androidx.navigation:navigation-ui-ktx:$rootProject.navigationVersion"
45 |
46 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$rootProject.kotlinVersion"
47 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutinesVersion"
48 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutinesVersion"
49 |
50 | implementation "com.google.firebase:firebase-core:$rootProject.firebaseCore"
51 | implementation "com.google.firebase:firebase-auth:$rootProject.firebaseAuth"
52 | implementation "com.google.android.gms:play-services-auth:$rootProject.playServicesAuth"
53 |
54 | implementation "com.google.firebase:firebase-firestore:$rootProject.firebaseFirestore"
55 | implementation "androidx.room:room-runtime:$rootProject.room"
56 | implementation "androidx.room:room-ktx:$rootProject.room"
57 |
58 |
59 | kapt "androidx.room:room-compiler:$rootProject.room"
60 |
61 | testImplementation "org.junit.jupiter:junit-jupiter-api:$rootProject.junitVersion"
62 | testImplementation "org.junit.jupiter:junit-jupiter-engine:$rootProject.junitVersion"
63 | }
64 |
65 | apply plugin: 'com.google.gms.google-services'
66 |
--------------------------------------------------------------------------------
/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
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/common/AndroidExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.common
2 |
3 | import android.graphics.drawable.AnimationDrawable
4 | import android.widget.Toast
5 | import androidx.fragment.app.Fragment
6 |
7 | internal fun Fragment.makeToast(value: String) {
8 | Toast.makeText(activity, value, Toast.LENGTH_SHORT).show()
9 | }
10 |
11 | internal fun AnimationDrawable.startWithFade(){
12 | this.setEnterFadeDuration(1000)
13 | this.setExitFadeDuration(1000)
14 | this.start()
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/common/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.common
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.ViewModel
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.Job
8 | import kotlin.coroutines.CoroutineContext
9 |
10 | abstract class BaseViewModel(protected val uiContext: CoroutineContext) : ViewModel(), CoroutineScope {
11 | abstract fun handleEvent(event: T)
12 |
13 | //cancellation
14 | protected lateinit var jobTracker: Job
15 |
16 | init {
17 | jobTracker = Job()
18 | }
19 |
20 | //suggestion from Al Warren: to promote encapsulation and immutability, hide the MutableLiveData objects behind
21 | //LiveData references:
22 | protected val errorState = MutableLiveData()
23 | val error: LiveData get() = errorState
24 |
25 | protected val loadingState = MutableLiveData()
26 | val loading: LiveData get() = loadingState
27 |
28 | override val coroutineContext: CoroutineContext
29 | get() = uiContext + jobTracker
30 |
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/common/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.common
2 |
3 | internal const val LOGIN_ERROR = "Error retrieving user."
4 | internal const val LOADING = "Loading..."
5 | internal const val LOGOUT_ERROR = "Error logging out user."
6 | internal const val GET_NOTE_ERROR = "Error retrieving note."
7 | internal const val GET_NOTES_ERROR = "Error retrieving notes."
8 | internal const val SIGN_OUT = "SIGN OUT"
9 | internal const val SIGN_IN = "SIGN IN"
10 | internal const val SIGNED_IN = "Signed In"
11 | internal const val SIGNED_OUT = "Signed Out"
12 | internal const val ERROR_NETWORK_UNAVAILABLE = "Network Unavailable"
13 | internal const val ERROR_AUTH = "An Error Has Occured"
14 | internal const val RETRY = "RETRY"
15 | internal const val ANTENNA_EMPTY = "ic_antenna_empty"
16 | internal const val ANTENNA_FULL = "ic_antenna_full"
17 | internal const val ANTENNA_LOOP = "antenna_loop_fast"
18 |
19 | /**
20 | * This value is just a constant to denote our sign in request; It can be any int.
21 | * Would have been great if that was explained in the docs, I assumed at first that it had to
22 | * be a specific value.
23 | */
24 | internal const val RC_SIGN_IN = 1337
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/common/DataExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.common
2 |
3 | import android.text.Editable
4 | import com.google.android.gms.tasks.Task
5 | import com.google.firebase.auth.FirebaseUser
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.FirebaseNote
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.Note
8 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.RoomNote
9 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.User
10 | import kotlin.coroutines.resume
11 | import kotlin.coroutines.resumeWithException
12 | import kotlin.coroutines.suspendCoroutine
13 |
14 | internal suspend fun awaitTaskResult(task: Task): T = suspendCoroutine { continuation ->
15 | task.addOnCompleteListener { task ->
16 | if (task.isSuccessful) {
17 | continuation.resume(task.result!!)
18 | } else {
19 | continuation.resumeWithException(task.exception!!)
20 | }
21 | }
22 | }
23 |
24 | //Wraps Firebase/GMS calls
25 | internal suspend fun awaitTaskCompletable(task: Task): Unit = suspendCoroutine { continuation ->
26 | task.addOnCompleteListener { task ->
27 | if (task.isSuccessful) {
28 | continuation.resume(Unit)
29 | } else {
30 | continuation.resumeWithException(task.exception!!)
31 | }
32 | }
33 | }
34 |
35 | internal val FirebaseUser.toUser: User
36 | get() = User(
37 | uid = this.uid,
38 | name = this.displayName ?: ""
39 | )
40 |
41 | internal val FirebaseNote.toNote: Note
42 | get() = Note(
43 | this.creationDate ?: "",
44 | this.contents ?: "",
45 | this.upVotes ?: 0,
46 | this.imageurl ?: "",
47 | User(this.creator ?: "")
48 | )
49 |
50 | internal val Note.toFirebaseNote: FirebaseNote
51 | get() = FirebaseNote(
52 | this.creationDate,
53 | this.contents,
54 | this.upVotes,
55 | this.imageUrl,
56 | this.safeGetUid
57 | )
58 |
59 | internal val RoomNote.toNote: Note
60 | get() = Note(
61 | this.creationDate,
62 | this.contents,
63 | this.upVotes,
64 | this.imageUrl,
65 | User(this.creatorId)
66 | )
67 |
68 | internal val Note.toRoomNote: RoomNote
69 | get() = RoomNote(
70 | this.creationDate,
71 | this.contents,
72 | this.upVotes,
73 | this.imageUrl,
74 | this.safeGetUid
75 | )
76 |
77 | internal fun List.toNoteListFromRoomNote(): List = this.flatMap {
78 | listOf(it.toNote)
79 | }
80 |
81 | internal fun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this)
82 |
83 | internal val Note.safeGetUid: String
84 | get() = this.creator?.uid ?: ""
85 |
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/common/Result.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.common
2 |
3 | /**
4 | * Result Wrapper
5 | */
6 | sealed class Result {
7 |
8 | data class Value(val value: V) : Result()
9 | data class Error(val error: E) : Result()
10 |
11 | companion object Factory{
12 | inline fun build(function: () -> V): Result =
13 | try {
14 | Value(function.invoke())
15 | } catch (e: java.lang.Exception) {
16 | Error(e)
17 | }
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/login/LoginActivity.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.login
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.google.firebase.FirebaseApp
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.R
7 |
8 | private const val LOGIN_VIEW = "LOGIN_VIEW"
9 |
10 | class LoginActivity : AppCompatActivity() {
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setContentView(R.layout.activity_login)
15 | FirebaseApp.initializeApp(applicationContext)
16 |
17 | val view = supportFragmentManager.findFragmentByTag(LOGIN_VIEW) as LoginView?
18 | ?: LoginView()
19 |
20 | supportFragmentManager.beginTransaction()
21 | .replace(R.id.root_activity_login, view, LOGIN_VIEW)
22 | .commitNowAllowingStateLoss()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/login/LoginEvent.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.login
2 |
3 | sealed class LoginEvent {
4 | object OnAuthButtonClick : LoginEvent()
5 | object OnStart : LoginEvent()
6 | data class OnGoogleSignInResult(val result: LoginResult) : LoginEvent()
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/login/LoginView.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.login
2 |
3 |
4 | import android.content.Intent
5 | import android.graphics.drawable.AnimationDrawable
6 | import android.os.Bundle
7 | import android.util.Log
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import androidx.activity.OnBackPressedCallback
12 | import androidx.activity.addCallback
13 | import androidx.fragment.app.Fragment
14 | import androidx.lifecycle.Observer
15 | import androidx.lifecycle.ViewModelProvider
16 | import com.google.android.gms.auth.api.signin.GoogleSignIn
17 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount
18 | import com.google.android.gms.auth.api.signin.GoogleSignInOptions
19 | import com.google.android.gms.common.api.ApiException
20 | import com.wiseassblog.jetpacknotesmvvmkotlin.R
21 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.ANTENNA_LOOP
22 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.RC_SIGN_IN
23 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.startWithFade
24 | import com.wiseassblog.jetpacknotesmvvmkotlin.login.buildlogic.LoginInjector
25 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.LoginResult
26 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.NoteActivity
27 | import kotlinx.android.synthetic.main.fragment_login.*
28 |
29 | //Note: if you want to support more than just English, you'll want to use Strings.xml instead of const val
30 |
31 |
32 | class LoginView : Fragment() {
33 |
34 | private lateinit var viewModel: UserViewModel
35 |
36 | override fun onCreateView(
37 | inflater: LayoutInflater, container: ViewGroup?,
38 | savedInstanceState: Bundle?
39 | ): View? {
40 | return inflater.inflate(R.layout.fragment_login, container, false)
41 | }
42 |
43 | //Create and bind to ViewModel
44 | override fun onStart() {
45 | super.onStart()
46 | viewModel = ViewModelProvider(
47 | this,
48 | LoginInjector(requireActivity().application).provideUserViewModelFactory()
49 | ).get(UserViewModel::class.java)
50 |
51 | //start background anim
52 | (root_fragment_login.background as AnimationDrawable).startWithFade()
53 |
54 | setUpClickListeners()
55 | observeViewModel()
56 |
57 | viewModel.handleEvent(LoginEvent.OnStart)
58 | }
59 |
60 | private fun setUpClickListeners() {
61 | btn_auth_attempt.setOnClickListener { viewModel.handleEvent(LoginEvent.OnAuthButtonClick) }
62 |
63 | imb_toolbar_back.setOnClickListener { startListActivity() }
64 |
65 | requireActivity().onBackPressedDispatcher.addCallback(this) {
66 | startListActivity()
67 | }
68 |
69 |
70 | }
71 |
72 | private fun observeViewModel() {
73 | viewModel.signInStatusText.observe(
74 | viewLifecycleOwner,
75 | Observer {
76 | //"it" is the alue of the MutableLiveData object, which is inferred to be a String automatically
77 | lbl_login_status_display.text = it
78 | }
79 | )
80 |
81 | viewModel.authButtonText.observe(
82 | viewLifecycleOwner,
83 | Observer {
84 | btn_auth_attempt.text = it
85 | }
86 | )
87 |
88 | viewModel.startAnimation.observe(
89 | viewLifecycleOwner,
90 | Observer {
91 | imv_antenna_animation.setImageResource(
92 | resources.getIdentifier(ANTENNA_LOOP, "drawable", activity?.packageName)
93 | )
94 | (imv_antenna_animation.drawable as AnimationDrawable).start()
95 | }
96 | )
97 |
98 | viewModel.authAttempt.observe(
99 | viewLifecycleOwner,
100 | Observer { startSignInFlow() }
101 | )
102 |
103 | viewModel.satelliteDrawable.observe(
104 | viewLifecycleOwner,
105 | Observer {
106 | imv_antenna_animation.setImageResource(
107 | resources.getIdentifier(it, "drawable", activity?.packageName)
108 | )
109 | }
110 | )
111 | }
112 |
113 | private fun startListActivity() = requireActivity().startActivity(
114 | Intent(
115 | activity,
116 | NoteActivity::class.java
117 | )
118 | )
119 |
120 | private fun startSignInFlow() {
121 | val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
122 | .requestIdToken(getString(R.string.default_web_client_id))
123 | .build()
124 |
125 | val googleSignInClient = GoogleSignIn.getClient(requireActivity(), gso)
126 |
127 | val signInIntent = googleSignInClient.signInIntent
128 | startActivityForResult(signInIntent, RC_SIGN_IN)
129 | }
130 |
131 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
132 | super.onActivityResult(requestCode, resultCode, data)
133 |
134 | val task = GoogleSignIn.getSignedInAccountFromIntent(data)
135 | var userToken: String? = null
136 |
137 | try {
138 | val account: GoogleSignInAccount? = task.getResult(ApiException::class.java)
139 |
140 | if (account != null) userToken = account.idToken
141 | } catch (exception: Exception) {
142 | Log.d("LOGIN", exception.toString())
143 | }
144 |
145 | viewModel.handleEvent(
146 | LoginEvent.OnGoogleSignInResult(
147 | LoginResult(
148 | requestCode,
149 | userToken
150 | )
151 | )
152 | )
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/login/UserViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.login
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.*
5 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.LoginResult
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.User
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.IUserRepository
8 | import kotlinx.coroutines.launch
9 | import kotlin.coroutines.CoroutineContext
10 |
11 | /**
12 | * This approach to ViewModels reduces the complexity of the View by containing specific details about widgets and
13 | * controls present in the View. The benefit of doing so is to make the View in to a Humble Object; reducing or
14 | * eliminating the need to test the View.
15 | *
16 | * The downside of this approach, is that the ViewModel is no longer re-usable across a variety of Views. In this case,
17 | * since this ViewModel is only used by a single View, and the application architecture will not change any time soon,
18 | * losing re-usability in exchange for a simpler View is not a problem.
19 | */
20 | class UserViewModel(
21 | val repo: IUserRepository,
22 | uiContext: CoroutineContext
23 | ) : BaseViewModel>(uiContext) {
24 |
25 | //The actual data model is kept private to avoid unwanted tampering
26 | private val userState = MutableLiveData()
27 |
28 | //Control Logic
29 | internal val authAttempt = MutableLiveData()
30 | internal val startAnimation = MutableLiveData()
31 |
32 | //UI Binding
33 | internal val signInStatusText = MutableLiveData()
34 | internal val authButtonText = MutableLiveData()
35 | internal val satelliteDrawable = MutableLiveData()
36 |
37 | private fun showErrorState() {
38 | signInStatusText.value = LOGIN_ERROR
39 | authButtonText.value = SIGN_IN
40 | satelliteDrawable.value = ANTENNA_EMPTY
41 | }
42 |
43 | private fun showLoadingState() {
44 | signInStatusText.value = LOADING
45 | satelliteDrawable.value = ANTENNA_LOOP
46 | startAnimation.value = Unit
47 | }
48 |
49 | private fun showSignedInState() {
50 | signInStatusText.value = SIGNED_IN
51 | authButtonText.value = SIGN_OUT
52 | satelliteDrawable.value = ANTENNA_FULL
53 | }
54 |
55 | private fun showSignedOutState() {
56 | signInStatusText.value = SIGNED_OUT
57 | authButtonText.value = SIGN_IN
58 | satelliteDrawable.value = ANTENNA_EMPTY
59 | }
60 |
61 | override fun handleEvent(event: LoginEvent) {
62 | //Trigger loading screen first
63 | showLoadingState()
64 | when (event) {
65 | is LoginEvent.OnStart -> getUser()
66 | is LoginEvent.OnAuthButtonClick -> onAuthButtonClick()
67 | is LoginEvent.OnGoogleSignInResult -> onSignInResult(event.result)
68 | }
69 | }
70 |
71 | private fun getUser() = launch {
72 | val result = repo.getCurrentUser()
73 | when (result) {
74 | is Result.Value -> {
75 | userState.value = result.value
76 | if (result.value == null) showSignedOutState()
77 | else showSignedInState()
78 | }
79 | is Result.Error -> showErrorState()
80 | }
81 | }
82 |
83 | /**
84 | * If user is null, tell the View to begin the authAttempt. Else, attempt to sign the user out
85 | */
86 | private fun onAuthButtonClick() {
87 | if (userState.value == null) authAttempt.value = Unit
88 | else signOutUser()
89 | }
90 |
91 | private fun onSignInResult(result: LoginResult) = launch {
92 | if (result.requestCode == RC_SIGN_IN && result.userToken != null) {
93 |
94 | val createGoogleUserResult = repo.signInGoogleUser(
95 | result.userToken
96 | )
97 |
98 | //Result.Value means it was successful
99 | if (createGoogleUserResult is Result.Value) getUser()
100 | else showErrorState()
101 | } else {
102 | showErrorState()
103 | }
104 | }
105 |
106 | private fun signOutUser() = launch {
107 | val result = repo.signOutCurrentUser()
108 |
109 | when (result) {
110 | is Result.Value -> {
111 | userState.value = null
112 | showSignedOutState()
113 | }
114 | is Result.Error -> showErrorState()
115 | }
116 | }
117 |
118 |
119 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/login/buildlogic/LoginInjector.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.login.buildlogic
2 |
3 | import android.app.Application
4 | import androidx.lifecycle.AndroidViewModel
5 | import com.google.firebase.FirebaseApp
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.implementations.FirebaseUserRepoImpl
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.IUserRepository
8 |
9 | class LoginInjector(application: Application): AndroidViewModel(application) {
10 |
11 | init {
12 | FirebaseApp.initializeApp(application)
13 | }
14 |
15 | private fun getUserRepository(): IUserRepository {
16 | return FirebaseUserRepoImpl()
17 | }
18 |
19 | fun provideUserViewModelFactory(): UserViewModelFactory =
20 | UserViewModelFactory(
21 | getUserRepository()
22 | )
23 |
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/login/buildlogic/UserViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.login.buildlogic
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.wiseassblog.jetpacknotesmvvmkotlin.login.UserViewModel
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.IUserRepository
7 | import kotlinx.coroutines.Dispatchers
8 |
9 | class UserViewModelFactory(
10 | private val userRepo: IUserRepository
11 | ): ViewModelProvider.NewInstanceFactory() {
12 |
13 | @Suppress("UNCHECKED_CAST")
14 | override fun create(modelClass: Class): T {
15 |
16 | return UserViewModel(userRepo, Dispatchers.Main) as T
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/FirebaseNote.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model
2 |
3 | data class FirebaseNote(
4 | val creationDate: String? = "",
5 | val contents: String? = "",
6 | val upVotes: Int? = 0,
7 | val imageurl: String? = "",
8 | val creator: String? = ""
9 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/LoginResult.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model
2 |
3 | /**
4 | * Wrapper class for data recieved in LoginActivity's onActivityResult()
5 | * function
6 | */
7 | data class LoginResult(val requestCode: Int, val userToken: String?)
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/Note.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model
2 |
3 | data class Note(val creationDate:String,
4 | val contents:String,
5 | val upVotes: Int,
6 | val imageUrl: String,
7 | val creator: User?)
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/NoteDao.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model
2 |
3 | import androidx.room.*
4 |
5 |
6 | @Dao
7 | interface NoteDao {
8 | @Query("SELECT * FROM notes")
9 | suspend fun getNotes(): List
10 |
11 | @Query("SELECT * FROM notes WHERE creation_date = :creationDate")
12 | suspend fun getNoteById(creationDate: String): RoomNote
13 |
14 | @Delete
15 | suspend fun deleteNote(note: RoomNote)
16 |
17 | @Insert(onConflict = OnConflictStrategy.REPLACE)
18 | suspend fun insertOrUpdateNote(note: RoomNote): Long
19 | }
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | //@Dao
30 | //interface NoteDao {
31 | // @Query("SELECT * FROM notes")
32 | // suspend fun getNotes(): List
33 | //
34 | // @Query("SELECT * FROM notes WHERE creation_date = :creationDate")
35 | // suspend fun getNoteById(creationDate: String): RoomNote
36 | //
37 | // @Delete
38 | // suspend fun deleteNote(note: RoomNote)
39 | //
40 | // //if update successful, will return number of rows effected, which should be 1
41 | // @Insert(onConflict = OnConflictStrategy.REPLACE)
42 | // suspend fun insertOrUpdateNote(note: RoomNote): Long
43 | //}
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/RoomNote.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.Index
6 | import androidx.room.PrimaryKey
7 |
8 |
9 | @Entity(
10 | tableName = "notes",
11 | indices = [Index("creation_date")]
12 | )
13 | data class RoomNote(
14 | @PrimaryKey
15 | @ColumnInfo(name = "creation_date")
16 | val creationDate: String,
17 |
18 | @ColumnInfo(name = "contents")
19 | val contents: String,
20 |
21 | @ColumnInfo(name = "up_votes")
22 | val upVotes: Int,
23 |
24 | @ColumnInfo(name = "image_url")
25 | val imageUrl: String,
26 |
27 | @ColumnInfo(name = "creator_id")
28 | val creatorId: String
29 | )
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/RoomNoteDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model
2 |
3 | import android.content.Context
4 | import androidx.room.Database
5 | import androidx.room.Room
6 | import androidx.room.RoomDatabase
7 |
8 | private const val DATABASE = "notes"
9 |
10 | @Database(
11 | entities = [RoomNote::class],
12 | version = 1,
13 | exportSchema = false
14 | )
15 | abstract class RoomNoteDatabase : RoomDatabase() {
16 |
17 | abstract fun roomNoteDao(): NoteDao
18 |
19 | //code below courtesy of https://github.com/googlesamples/android-sunflower; it is open
20 | //source just like this application.
21 | companion object {
22 |
23 | // For Singleton instantiation
24 | @Volatile
25 | private var instance: RoomNoteDatabase? = null
26 |
27 | fun getInstance(context: Context): RoomNoteDatabase {
28 | return instance ?: synchronized(this) {
29 | instance
30 | ?: buildDatabase(context).also { instance = it }
31 | }
32 | }
33 |
34 | private fun buildDatabase(context: Context): RoomNoteDatabase {
35 | return Room.databaseBuilder(context, RoomNoteDatabase::class.java, DATABASE)
36 | .build()
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/User.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model
2 |
3 | data class User(val uid: String,
4 | val name: String = "")
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/implementations/FirebaseUserRepoImpl.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model.implementations
2 |
3 | import com.google.firebase.auth.FirebaseAuth
4 | import com.google.firebase.auth.GoogleAuthProvider
5 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.Result
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.awaitTaskCompletable
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.User
8 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.IUserRepository
9 | import kotlinx.coroutines.Dispatchers
10 | import kotlinx.coroutines.withContext
11 |
12 | class FirebaseUserRepoImpl(val auth: FirebaseAuth = FirebaseAuth.getInstance()) : IUserRepository {
13 |
14 | override suspend fun signInGoogleUser(idToken: String):
15 | Result = withContext(Dispatchers.IO) {
16 | try {
17 | val credential = GoogleAuthProvider.getCredential(idToken, null)
18 | awaitTaskCompletable(auth.signInWithCredential(credential))
19 |
20 | Result.build { Unit }
21 | } catch (exception: Exception) {
22 | Result.build { throw exception }
23 | }
24 |
25 | }
26 |
27 |
28 | override suspend fun signOutCurrentUser(): Result {
29 | return Result.build {
30 | auth.signOut()
31 | }
32 | }
33 |
34 | override suspend fun getCurrentUser(): Result {
35 | val firebaseUser = auth.currentUser
36 |
37 | return if (firebaseUser == null) {
38 | Result.build { null }
39 | } else {
40 | Result.build {
41 | User(
42 | firebaseUser.uid,
43 | firebaseUser.displayName ?: ""
44 | )
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/implementations/NoteRepoImpl.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model.implementations
2 |
3 | import com.google.firebase.auth.FirebaseAuth
4 | import com.google.firebase.firestore.FirebaseFirestore
5 | import com.google.firebase.firestore.QuerySnapshot
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.*
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.FirebaseNote
8 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.Note
9 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.NoteDao
10 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.User
11 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.INoteRepository
12 |
13 | private const val COLLECTION_NAME = "notes"
14 |
15 | /**
16 | * If this wasn't a demo project, I would apply more abstraction to this repository (i.e. local and remote would be
17 | * separate interfaces which this class would depend on). I wanted to keep it the back end simple since this app is
18 | * a demo on MVVM, which is a front end architecture pattern.
19 | */
20 | class NoteRepoImpl(
21 | val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance(),
22 | val remote: FirebaseFirestore = FirebaseFirestore.getInstance(),
23 | val local: NoteDao
24 | ) : INoteRepository {
25 |
26 |
27 | override suspend fun getNoteById(noteId: String): Result {
28 | val user = getActiveUser()
29 | return if (user != null) getRemoteNote(noteId, user)
30 | else getLocalNote(noteId)
31 | }
32 |
33 | override suspend fun deleteNote(note: Note): Result {
34 | val user = getActiveUser()
35 | return if (user != null) deleteRemoteNote(note.copy(creator = user))
36 | else deleteLocalNote(note)
37 | }
38 |
39 | override suspend fun updateNote(note: Note): Result {
40 | val user = getActiveUser()
41 | return if (user != null) updateRemoteNote(note.copy(creator = user))
42 | else updateLocalNote(note)
43 | }
44 |
45 | override suspend fun getNotes(): Result> {
46 | val user = getActiveUser()
47 | return if (user != null) getRemoteNotes(user)
48 | else getLocalNotes()
49 | }
50 |
51 | /**
52 | * if currentUser != null, return true
53 | */
54 | private fun getActiveUser(): User? {
55 | return firebaseAuth.currentUser?.toUser
56 | }
57 |
58 |
59 | private fun resultToNoteList(result: QuerySnapshot?): Result> {
60 | val noteList = mutableListOf()
61 |
62 | result?.forEach { documentSnapshot ->
63 | noteList.add(documentSnapshot.toObject(FirebaseNote::class.java).toNote)
64 | }
65 |
66 | return Result.build {
67 | noteList
68 | }
69 | }
70 |
71 |
72 | /* Remote Datasource */
73 |
74 | private suspend fun getRemoteNotes(user: User): Result> {
75 | return try {
76 | val task = awaitTaskResult(
77 | remote.collection(COLLECTION_NAME)
78 | .whereEqualTo("creator", user.uid)
79 | .get()
80 | )
81 |
82 | resultToNoteList(task)
83 | } catch (exception: Exception) {
84 | Result.build { throw exception }
85 | }
86 | }
87 |
88 | private suspend fun getRemoteNote(creationDate: String, user: User): Result {
89 | return try {
90 | val task = awaitTaskResult(
91 | remote.collection(COLLECTION_NAME)
92 | .document(creationDate + user.uid)
93 | .get()
94 | )
95 |
96 | Result.build {
97 | //Task
98 | task.toObject(FirebaseNote::class.java)?.toNote ?: throw Exception()
99 | }
100 | } catch (exception: Exception) {
101 | Result.build { throw exception }
102 | }
103 | }
104 |
105 | private suspend fun deleteRemoteNote(note: Note): Result = Result.build {
106 | awaitTaskCompletable(
107 | remote.collection(COLLECTION_NAME)
108 | .document(note.creationDate + note.creator!!.uid)
109 | .delete()
110 | )
111 | }
112 |
113 | /**
114 | * Notes are stored with the following composite document name:
115 | * note.creationDate + note.creator.uid
116 | * The reason for this, is that if I just used the creationDate, hypothetically two users
117 | * creating a note at the same time, would have duplicate entries in the cloud database :(
118 | */
119 | private suspend fun updateRemoteNote(note: Note): Result {
120 | return try {
121 | awaitTaskCompletable(
122 | remote.collection(COLLECTION_NAME)
123 | .document(note.creationDate + note.creator!!.uid)
124 | .set(note.toFirebaseNote)
125 | )
126 |
127 | Result.build { Unit }
128 |
129 | } catch (exception: Exception) {
130 | Result.build { throw exception }
131 | }
132 | }
133 |
134 | /* Local Datasource */
135 | private suspend fun getLocalNotes(): Result> = Result.build {
136 | local.getNotes().toNoteListFromRoomNote()
137 | }
138 |
139 | private suspend fun getLocalNote(id: String): Result = Result.build {
140 | local.getNoteById(id).toNote
141 | }
142 |
143 | private suspend fun deleteLocalNote(note: Note): Result = Result.build {
144 | local.deleteNote(note.toRoomNote)
145 | Unit
146 | }
147 |
148 | private suspend fun updateLocalNote(note: Note): Result = Result.build {
149 | local.insertOrUpdateNote(note.toRoomNote)
150 | Unit
151 | }
152 |
153 |
154 |
155 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/repository/INoteRepository.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model.repository
2 |
3 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.Result
4 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.Note
5 |
6 | interface INoteRepository {
7 | suspend fun getNoteById(noteId: String): Result
8 | suspend fun getNotes(): Result>
9 | suspend fun deleteNote(note: Note): Result
10 | suspend fun updateNote(note: Note): Result
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/model/repository/IUserRepository.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.model.repository
2 |
3 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.Result
4 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.User
5 |
6 | interface IUserRepository {
7 | suspend fun getCurrentUser(): Result
8 |
9 | suspend fun signOutCurrentUser(): Result
10 |
11 | suspend fun signInGoogleUser(idToken: String): Result
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/NoteActivity.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.navigation.NavController
6 | import androidx.navigation.Navigation
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.R
8 |
9 | class NoteActivity : AppCompatActivity() {
10 |
11 | private lateinit var nav: NavController
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_note)
16 |
17 | nav = Navigation.findNavController(this, R.id.fragment_nav)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/NoteListViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.BaseViewModel
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.GET_NOTES_ERROR
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.Result
8 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.Note
9 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.INoteRepository
10 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.notelist.NoteListEvent
11 | import kotlinx.coroutines.launch
12 | import kotlin.coroutines.CoroutineContext
13 |
14 | class NoteListViewModel(
15 | val noteRepo: INoteRepository,
16 | uiContext: CoroutineContext
17 | ) : BaseViewModel(uiContext) {
18 |
19 | private val noteListState = MutableLiveData>()
20 | val noteList: LiveData> get() = noteListState
21 |
22 | private val editNoteState = MutableLiveData()
23 | val editNote: LiveData get() = editNoteState
24 |
25 |
26 | override fun handleEvent(event: NoteListEvent) {
27 | when (event) {
28 | is NoteListEvent.OnStart -> getNotes()
29 | is NoteListEvent.OnNoteItemClick -> editNote(event.position)
30 | }
31 | }
32 |
33 | private fun editNote(position: Int) {
34 | editNoteState.value = noteList.value!![position].creationDate
35 | }
36 |
37 | private fun getNotes() = launch {
38 | val notesResult = noteRepo.getNotes()
39 |
40 | when (notesResult) {
41 | is Result.Value -> noteListState.value = notesResult.value
42 | is Result.Error -> errorState.value = GET_NOTES_ERROR
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/NoteListViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/NoteViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.BaseViewModel
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.GET_NOTE_ERROR
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.Result
8 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.Note
9 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.INoteRepository
10 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.notedetail.NoteDetailEvent
11 | import kotlinx.coroutines.launch
12 | import java.text.SimpleDateFormat
13 | import java.util.*
14 | import kotlin.coroutines.CoroutineContext
15 |
16 | class NoteViewModel(
17 | val noteRepo: INoteRepository,
18 | uiContext: CoroutineContext
19 | ) : BaseViewModel(uiContext) {
20 |
21 | private val noteState = MutableLiveData()
22 | val note: LiveData get() = noteState
23 |
24 | private val deletedState = MutableLiveData()
25 | val deleted: LiveData get() = deletedState
26 |
27 | private val updatedState = MutableLiveData()
28 | val updated: LiveData get() = updatedState
29 |
30 | override fun handleEvent(event: NoteDetailEvent) {
31 | when (event) {
32 | is NoteDetailEvent.OnStart -> getNote(event.noteId)
33 | is NoteDetailEvent.OnDeleteClick -> onDelete()
34 | is NoteDetailEvent.OnDoneClick -> updateNote(event.contents)
35 | }
36 | }
37 |
38 | private fun onDelete() = launch {
39 | val deleteResult = noteRepo.deleteNote(note.value!!)
40 |
41 | when (deleteResult) {
42 | is Result.Value -> deletedState.value = true
43 | is Result.Error -> deletedState.value = false
44 | }
45 | }
46 |
47 |
48 | private fun updateNote(contents: String) = launch {
49 | val updateResult = noteRepo.updateNote(
50 | note.value!!
51 | .copy(contents = contents)
52 | )
53 |
54 | when (updateResult) {
55 | is Result.Value -> updatedState.value = true
56 | is Result.Error -> updatedState.value = false
57 | }
58 | }
59 |
60 | private fun getNote(noteId: String) = launch {
61 | if (noteId == "") newNote()
62 | else {
63 | val noteResult = noteRepo.getNoteById(noteId)
64 |
65 | when (noteResult) {
66 | is Result.Value -> noteState.value = noteResult.value
67 | is Result.Error -> errorState.value = GET_NOTE_ERROR
68 | }
69 | }
70 | }
71 |
72 | private fun newNote() {
73 | noteState.value =
74 | Note(getCalendarTime(), "", 0, "rocket_loop", null)
75 | }
76 |
77 |
78 | private fun getCalendarTime(): String {
79 | val cal = Calendar.getInstance(TimeZone.getDefault())
80 | val format = SimpleDateFormat("d MMM yyyy HH:mm:ss Z")
81 | format.timeZone = cal.timeZone
82 | return format.format(cal.time)
83 | }
84 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/NoteViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notedetail/NoteDetailEvent.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notedetail
2 |
3 | sealed class NoteDetailEvent {
4 | data class OnDoneClick(val contents: String) : NoteDetailEvent()
5 | object OnDeleteClick : NoteDetailEvent()
6 | object OnDeleteConfirmed : NoteDetailEvent()
7 | data class OnStart(val noteId: String) : NoteDetailEvent()
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notedetail/NoteDetailView.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notedetail
2 |
3 | import android.graphics.drawable.AnimationDrawable
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.activity.OnBackPressedCallback
9 | import androidx.activity.addCallback
10 | import androidx.fragment.app.Fragment
11 | import androidx.lifecycle.Observer
12 | import androidx.lifecycle.ViewModelProvider
13 | import androidx.lifecycle.ViewModelProviders
14 | import androidx.navigation.fragment.findNavController
15 | import com.wiseassblog.jetpacknotesmvvmkotlin.R
16 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.makeToast
17 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.startWithFade
18 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.toEditable
19 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.NoteViewModel
20 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.notedetail.buildlogic.NoteDetailInjector
21 | import kotlinx.android.synthetic.main.fragment_note_detail.*
22 |
23 | class NoteDetailView : Fragment() {
24 |
25 | private lateinit var viewModel: NoteViewModel
26 |
27 | override fun onCreateView(
28 | inflater: LayoutInflater, container: ViewGroup?,
29 | savedInstanceState: Bundle?
30 | ): View? {
31 | // Inflate the layout for this fragment
32 | return inflater.inflate(R.layout.fragment_note_detail, container, false)
33 | }
34 |
35 | override fun onStart() {
36 | super.onStart()
37 |
38 | viewModel = ViewModelProvider(
39 | this,
40 | NoteDetailInjector(requireActivity().application).provideNoteViewModelFactory()
41 | ).get(
42 | NoteViewModel::class.java
43 | )
44 |
45 | showLoadingState()
46 |
47 | imb_toolbar_done.setOnClickListener {
48 | viewModel.handleEvent(
49 | NoteDetailEvent.OnDoneClick(
50 | edt_note_detail_text.text.toString()
51 | )
52 | )
53 | }
54 |
55 | imb_toolbar_delete.setOnClickListener { viewModel.handleEvent(NoteDetailEvent.OnDeleteClick) }
56 |
57 | observeViewModel()
58 |
59 | (frag_note_detail.background as AnimationDrawable).startWithFade()
60 |
61 | viewModel.handleEvent(
62 | NoteDetailEvent.OnStart(
63 | //note NoteDetailViewArgs is genereted via Navigation component
64 | NoteDetailViewArgs.fromBundle(arguments!!).noteId
65 | )
66 | )
67 | }
68 |
69 | private fun observeViewModel() {
70 | viewModel.error.observe(
71 | viewLifecycleOwner,
72 | Observer { errorMessage ->
73 | showErrorState(errorMessage)
74 | }
75 | )
76 |
77 | viewModel.note.observe(
78 | viewLifecycleOwner,
79 | Observer { note ->
80 | edt_note_detail_text.text = note.contents.toEditable()
81 | }
82 | )
83 |
84 | viewModel.updated.observe(
85 | viewLifecycleOwner,
86 | Observer {
87 | findNavController().navigate(R.id.noteListView)
88 | }
89 | )
90 |
91 | viewModel.deleted.observe(
92 | viewLifecycleOwner,
93 | Observer {
94 | findNavController().navigate(R.id.noteListView)
95 | }
96 | )
97 |
98 | requireActivity().onBackPressedDispatcher.addCallback(this) {
99 | findNavController().navigate(R.id.noteListView)
100 | }
101 | }
102 |
103 | private fun showErrorState(errorMessage: String?) {
104 | makeToast(errorMessage!!)
105 | findNavController().navigate(R.id.noteListView)
106 | }
107 |
108 | private fun showLoadingState() {
109 | (imv_note_detail_satellite.drawable as AnimationDrawable).start()
110 | }
111 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notedetail/buildlogic/NoteDetailInjector.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notedetail.buildlogic
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import androidx.lifecycle.AndroidViewModel
6 | import com.google.firebase.FirebaseApp
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.RoomNoteDatabase
8 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.implementations.NoteRepoImpl
9 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.INoteRepository
10 |
11 | class NoteDetailInjector(application: Application): AndroidViewModel(application) {
12 |
13 | private fun getNoteRepository(): INoteRepository {
14 |
15 | FirebaseApp.initializeApp(getApplication())
16 | return NoteRepoImpl(
17 | local = RoomNoteDatabase.getInstance(getApplication()).roomNoteDao()
18 | )
19 | }
20 |
21 | fun provideNoteViewModelFactory(): NoteViewModelFactory =
22 | NoteViewModelFactory(
23 | getNoteRepository()
24 | )
25 |
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notedetail/buildlogic/NoteViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notedetail.buildlogic
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.INoteRepository
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.NoteViewModel
7 | import kotlinx.coroutines.Dispatchers
8 |
9 | class NoteViewModelFactory(
10 | private val noteRepo: INoteRepository
11 | ) : ViewModelProvider.NewInstanceFactory() {
12 |
13 | @Suppress("UNCHECKED_CAST")
14 | override fun create(modelClass: Class): T {
15 |
16 | return NoteViewModel(noteRepo, Dispatchers.Main) as T
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notelist/NoteDiffUtilCallback.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notelist
2 |
3 |
4 | import androidx.recyclerview.widget.DiffUtil
5 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.Note
6 |
7 | class NoteDiffUtilCallback : DiffUtil.ItemCallback(){
8 | override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean {
9 | return oldItem.creationDate == newItem.creationDate
10 | }
11 |
12 | override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean {
13 | return oldItem.creationDate == newItem.creationDate
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notelist/NoteListAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notelist
2 |
3 |
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.ImageView
8 | import android.widget.TextView
9 | import androidx.lifecycle.MutableLiveData
10 | import androidx.recyclerview.widget.ListAdapter
11 | import androidx.recyclerview.widget.RecyclerView
12 | import com.wiseassblog.jetpacknotesmvvmkotlin.R
13 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.Note
14 | import kotlinx.android.synthetic.main.item_note.view.*
15 |
16 | class NoteListAdapter(val event:MutableLiveData = MutableLiveData()): ListAdapter(NoteDiffUtilCallback()){
17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
18 | val inflater = LayoutInflater.from(parent.context)
19 |
20 | return NoteViewHolder(
21 | inflater.inflate(R.layout.item_note, parent, false)
22 | )
23 | }
24 |
25 | override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
26 | getItem(position).let { note ->
27 | holder.content.text = note.contents
28 | holder.date.text = note.creationDate
29 |
30 | holder.itemView.setOnClickListener {
31 | event.value = NoteListEvent.OnNoteItemClick(position)
32 | }
33 | }
34 | }
35 |
36 |
37 | class NoteViewHolder(root: View): RecyclerView.ViewHolder(root){
38 | var content: TextView = root.lbl_message
39 | var date: TextView = root.lbl_date_and_time
40 | }
41 | }
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notelist/NoteListEvent.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notelist
2 |
3 | sealed class NoteListEvent {
4 | data class OnNoteItemClick(val position: Int) : NoteListEvent()
5 | object OnNewNoteClick : NoteListEvent()
6 | object OnStart : NoteListEvent()
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notelist/NoteListView.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notelist
2 |
3 | import android.graphics.drawable.AnimationDrawable
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.fragment.app.Fragment
9 | import androidx.lifecycle.Observer
10 | import androidx.lifecycle.ViewModelProvider
11 | import androidx.lifecycle.ViewModelProviders
12 | import androidx.navigation.fragment.findNavController
13 | import com.wiseassblog.jetpacknotesmvvmkotlin.R
14 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.makeToast
15 | import com.wiseassblog.jetpacknotesmvvmkotlin.common.startWithFade
16 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.NoteListViewModel
17 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.notelist.buildlogic.NoteListInjector
18 | import kotlinx.android.synthetic.main.fragment_note_list.*
19 |
20 | class NoteListView : Fragment() {
21 |
22 | private lateinit var viewModel: NoteListViewModel
23 | private lateinit var adapter: NoteListAdapter
24 |
25 | override fun onCreateView(
26 | inflater: LayoutInflater, container: ViewGroup?,
27 | savedInstanceState: Bundle?
28 | ): View? {
29 | // Inflate the layout for this fragment
30 | return inflater.inflate(R.layout.fragment_note_list, container, false)
31 | }
32 |
33 | override fun onDestroyView() {
34 | super.onDestroyView()
35 | //THIS IS IMPORTANT!!!
36 | rec_list_fragment.adapter = null
37 | }
38 |
39 | override fun onStart() {
40 | super.onStart()
41 | viewModel = ViewModelProvider(
42 | this,
43 | NoteListInjector(requireActivity().application).provideNoteListViewModelFactory()
44 | ).get(
45 | NoteListViewModel::class.java
46 | )
47 |
48 | (imv_space_background.drawable as AnimationDrawable).startWithFade()
49 |
50 | showLoadingState()
51 | setUpAdapter()
52 | observeViewModel()
53 |
54 | fab_create_new_item.setOnClickListener {
55 | val direction = NoteListViewDirections.actionNoteListViewToNoteDetailView("")
56 | findNavController().navigate(direction)
57 | }
58 |
59 | imv_toolbar_auth.setOnClickListener {
60 | findNavController().navigate(R.id.loginView)
61 | }
62 |
63 | viewModel.handleEvent(
64 | NoteListEvent.OnStart
65 | )
66 | }
67 |
68 | private fun setUpAdapter() {
69 | adapter = NoteListAdapter()
70 | adapter.event.observe(
71 | viewLifecycleOwner,
72 | Observer {
73 | viewModel.handleEvent(it)
74 | }
75 | )
76 |
77 | rec_list_fragment.adapter = adapter
78 | }
79 |
80 | private fun observeViewModel() {
81 | viewModel.error.observe(
82 | viewLifecycleOwner,
83 | Observer { errorMessage ->
84 | showErrorState(errorMessage)
85 | }
86 | )
87 |
88 | viewModel.noteList.observe(
89 | viewLifecycleOwner,
90 | Observer { noteList ->
91 | adapter.submitList(noteList)
92 |
93 | if (noteList.isNotEmpty()) {
94 | (imv_satellite_animation.drawable as AnimationDrawable).stop()
95 | imv_satellite_animation.visibility = View.INVISIBLE
96 | rec_list_fragment.visibility = View.VISIBLE
97 | }
98 | }
99 | )
100 |
101 | viewModel.editNote.observe(
102 | viewLifecycleOwner,
103 | Observer { noteId ->
104 | startNoteDetailWithArgs(noteId)
105 | }
106 | )
107 | }
108 |
109 | private fun startNoteDetailWithArgs(noteId: String) = findNavController().navigate(
110 | NoteListViewDirections.actionNoteListViewToNoteDetailView(noteId)
111 | )
112 |
113 |
114 | private fun showErrorState(errorMessage: String?) = makeToast(errorMessage!!)
115 |
116 |
117 | private fun showLoadingState() = (imv_satellite_animation.drawable as AnimationDrawable).start()
118 |
119 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notelist/buildlogic/NoteListInjector.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notelist.buildlogic
2 |
3 | import android.app.Application
4 | import androidx.lifecycle.AndroidViewModel
5 | import com.google.firebase.FirebaseApp
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.RoomNoteDatabase
7 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.implementations.NoteRepoImpl
8 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.INoteRepository
9 |
10 | class NoteListInjector(application:Application): AndroidViewModel(application) {
11 | private fun getNoteRepository(): INoteRepository {
12 | FirebaseApp.initializeApp(getApplication())
13 | return NoteRepoImpl(
14 | local = RoomNoteDatabase.getInstance(getApplication()).roomNoteDao()
15 | )
16 | }
17 |
18 | fun provideNoteListViewModelFactory(): NoteListViewModelFactory =
19 | NoteListViewModelFactory(
20 | getNoteRepository()
21 | )
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/wiseassblog/jetpacknotesmvvmkotlin/note/notelist/buildlogic/NoteListViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.wiseassblog.jetpacknotesmvvmkotlin.note.notelist.buildlogic
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.wiseassblog.jetpacknotesmvvmkotlin.model.repository.INoteRepository
6 | import com.wiseassblog.jetpacknotesmvvmkotlin.note.NoteListViewModel
7 | import kotlinx.coroutines.Dispatchers
8 |
9 | class NoteListViewModelFactory(
10 | private val noteRepo: INoteRepository
11 | ) : ViewModelProvider.NewInstanceFactory() {
12 |
13 | @Suppress("UNCHECKED_CAST")
14 | override fun create(modelClass: Class): T {
15 |
16 | return NoteListViewModel(noteRepo, Dispatchers.Main) as T
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-land/space_bg_one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/drawable-land/space_bg_one.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-land/space_bg_three.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/drawable-land/space_bg_three.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-land/space_bg_two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/drawable-land/space_bg_two.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/antenna_loop.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/antenna_loop_fast.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_access_time_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_antenna_empty.xml:
--------------------------------------------------------------------------------
1 |
3 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_antenna_full.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_antenna_half.xml:
--------------------------------------------------------------------------------
1 |
3 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_add_24px.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_event_24px.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_forever_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_done_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_visibility_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_visibility_off_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_vpn_key_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/im_rocket_one.xml:
--------------------------------------------------------------------------------
1 |
4 |
6 |
8 |
10 |
12 |
14 |
16 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/im_rocket_three.xml:
--------------------------------------------------------------------------------
1 |
4 |
6 |
8 |
10 |
12 |
14 |
16 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/im_rocket_two.xml:
--------------------------------------------------------------------------------
1 |
4 |
6 |
8 |
10 |
12 |
14 |
16 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rocket_loop.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/rocket_one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/drawable/rocket_one.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/space_bg_one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/drawable/space_bg_one.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/space_bg_three.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/drawable/space_bg_three.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/space_bg_two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/drawable/space_bg_two.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/space_loop.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_note.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
19 |
28 |
36 |
45 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_note_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
25 |
26 |
27 |
37 |
38 |
47 |
48 |
49 |
50 |
66 |
67 |
82 |
83 |
98 |
99 |
105 |
106 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_note_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
20 |
21 |
22 |
33 |
34 |
35 |
47 |
48 |
49 |
50 |
63 |
64 |
75 |
76 |
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_note.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
33 |
40 |
53 |
--------------------------------------------------------------------------------
/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.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BracketCove/JetpackNotesMvvmKotlin/511cdfa91ac82dbf1b3479feb90e1035e88c173c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
11 |
16 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #121212
4 | #000000
5 | #26A69A
6 | #121212
7 |
8 |
9 | #52000000
10 | #52FFFFFF
11 |
12 | #FF0000
13 | #0000FF
14 | #00FF00
15 | #FFEB3B
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | JetpackNotesMvvmKotlin
3 |
4 |
5 | Hello blank fragment
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/view_styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
11 |
12 |
16 |
17 |
22 |
23 |
27 |
28 |
29 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | buildscript {
3 | ext {
4 | compileSdkVersion = 30
5 | minSdkVersion = 21
6 | targetSdkVersion = 30
7 |
8 | playServicesAuth = "19.0.0"
9 | firebaseAuth = "20.0.1"
10 | firebaseCore = "18.0.0"
11 | firebaseFirestore = "22.0.1"
12 | constraintLayoutVersion = '2.1.0-alpha2'
13 | room = '2.3.0-alpha04'
14 | coroutinesVersion = "1.4.1"
15 | glideVersion = '4.8.0'
16 | gradleVersion = '3.3.2'
17 | junitVersion = '5.3.1'
18 | kotlinVersion = '1.4.10'
19 | ktlintVersion = '0.30.0'
20 | ktxVersion = '1.3.2'
21 | lifecycleVersion = '2.2.0'
22 | materialVersion = '1.2.1'
23 | navigationVersion = '2.3.2'
24 | recyclerViewVersion = '1.2.0-beta01'
25 | roomVersion = '2.1.0-alpha04'
26 | runnerVersion = '1.0.1'
27 | supportLibraryVersion = '1.2.0'
28 | }
29 |
30 | repositories {
31 | google()
32 | jcenter()
33 | maven {
34 | url 'https://maven.fabric.io/public'
35 | }
36 | }
37 | dependencies {
38 | classpath 'com.android.tools.build:gradle:4.1.1'
39 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
40 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
41 | classpath 'com.google.gms:google-services:4.3.4'
42 | classpath 'io.fabric.tools:gradle:1.28.0'
43 | }
44 | }
45 |
46 | allprojects {
47 | repositories {
48 | google()
49 | jcenter()
50 | maven {
51 | url 'https://maven.fabric.io/public'
52 | }
53 | }
54 | }
55 |
56 | task clean(type: Delete) {
57 | delete rootProject.buildDir
58 | }
59 |
60 |
61 |
--------------------------------------------------------------------------------
/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=-Xmx1536m
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 | # Kotlin code style for this project: "official" or "obsolete":
15 | kotlin.code.style=official
16 | android.useAndroidX=true
17 | android.enableJetifier = true
18 |
19 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------