diff --git a/app/build.gradle b/app/build.gradle index 55ca3e78..5a0dc49a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,6 +47,9 @@ dependencies { testImplementation "com.github.tomakehurst:wiremock-jre8:2.26.2" testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.annotation:annotation:1.1.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' @@ -73,4 +76,4 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'crea executionData = fileTree(dir: project.buildDir, includes: [ 'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/debugAndroidTest/connected/*coverage.ec' ]) -} \ No newline at end of file +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2d63dcda..09cf7eba 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,9 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> + diff --git a/app/src/main/java/com/h/pixeldroid/data/LoginDataSource.kt b/app/src/main/java/com/h/pixeldroid/data/LoginDataSource.kt new file mode 100644 index 00000000..7600b90a --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/data/LoginDataSource.kt @@ -0,0 +1,25 @@ +package com.h.pixeldroid.data + +import com.h.pixeldroid.data.model.LoggedInUser +import java.io.IOException + +/** + * Class that handles authentication w/ login credentials and retrieves user information. + */ +class LoginDataSource { + + fun login(username: String, password: String): Result { + try { + // TODO: handle loggedInUser authentication + val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe") + return Result.Success(fakeUser) + } catch (e: Throwable) { + return Result.Error(IOException("Error logging in", e)) + } + } + + fun logout() { + // TODO: revoke authentication + } +} + diff --git a/app/src/main/java/com/h/pixeldroid/data/LoginRepository.kt b/app/src/main/java/com/h/pixeldroid/data/LoginRepository.kt new file mode 100644 index 00000000..e9d33973 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/data/LoginRepository.kt @@ -0,0 +1,46 @@ +package com.h.pixeldroid.data + +import com.h.pixeldroid.data.model.LoggedInUser + +/** + * Class that requests authentication and user information from the remote data source and + * maintains an in-memory cache of login status and user credentials information. + */ + +class LoginRepository(val dataSource: LoginDataSource) { + + // in-memory cache of the loggedInUser object + var user: LoggedInUser? = null + private set + + val isLoggedIn: Boolean + get() = user != null + + init { + // If user credentials will be cached in local storage, it is recommended it be encrypted + // @see https://developer.android.com/training/articles/keystore + user = null + } + + fun logout() { + user = null + dataSource.logout() + } + + fun login(username: String, password: String): Result { + // handle login + val result = dataSource.login(username, password) + + if (result is Result.Success) { + setLoggedInUser(result.data) + } + + return result + } + + private fun setLoggedInUser(loggedInUser: LoggedInUser) { + this.user = loggedInUser + // If user credentials will be cached in local storage, it is recommended it be encrypted + // @see https://developer.android.com/training/articles/keystore + } +} diff --git a/app/src/main/java/com/h/pixeldroid/data/Result.kt b/app/src/main/java/com/h/pixeldroid/data/Result.kt new file mode 100644 index 00000000..d6094c18 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/data/Result.kt @@ -0,0 +1,18 @@ +package com.h.pixeldroid.data + +/** + * A generic class that holds a value with its loading status. + * @param + */ +sealed class Result { + + data class Success(val data: T) : Result() + data class Error(val exception: Exception) : Result() + + override fun toString(): String { + return when (this) { + is Success<*> -> "Success[data=$data]" + is Error -> "Error[exception=$exception]" + } + } +} diff --git a/app/src/main/java/com/h/pixeldroid/data/model/LoggedInUser.kt b/app/src/main/java/com/h/pixeldroid/data/model/LoggedInUser.kt new file mode 100644 index 00000000..0f2eaed6 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/data/model/LoggedInUser.kt @@ -0,0 +1,9 @@ +package com.h.pixeldroid.data.model + +/** + * Data class that captures user information for logged in users retrieved from LoginRepository + */ +data class LoggedInUser( + val userId: String, + val displayName: String +) diff --git a/app/src/main/java/com/h/pixeldroid/ui/login/LoggedInUserView.kt b/app/src/main/java/com/h/pixeldroid/ui/login/LoggedInUserView.kt new file mode 100644 index 00000000..68255c4c --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/ui/login/LoggedInUserView.kt @@ -0,0 +1,9 @@ +package com.h.pixeldroid.ui.login + +/** + * User details post authentication that is exposed to the UI + */ +data class LoggedInUserView( + val displayName: String + //... other data fields that may be accessible to the UI +) diff --git a/app/src/main/java/com/h/pixeldroid/ui/login/LoginActivity.kt b/app/src/main/java/com/h/pixeldroid/ui/login/LoginActivity.kt new file mode 100644 index 00000000..d8dbed38 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/ui/login/LoginActivity.kt @@ -0,0 +1,129 @@ +package com.h.pixeldroid.ui.login + +import android.app.Activity +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import android.os.Bundle +import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.Button +import android.widget.EditText +import android.widget.ProgressBar +import android.widget.Toast + +import com.h.pixeldroid.R + +class LoginActivity : AppCompatActivity() { + + private lateinit var loginViewModel: LoginViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_login) + + val username = findViewById(R.id.username) + val password = findViewById(R.id.password) + val login = findViewById