Handle login errors
This commit is contained in:
parent
29893f9ee7
commit
511a36516b
17 changed files with 68 additions and 37 deletions
|
@ -1,13 +1,11 @@
|
|||
package ing.bikeshedengineer.debtpirate
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import androidx.navigation.compose.NavHost
|
|||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import ing.bikeshedengineer.debtpirate.presentation.ui.auth.AuthScreen
|
||||
import ing.bikeshedengineer.debtpirate.presentation.theme.DebtPirateTheme
|
||||
import ing.bikeshedengineer.debtpirate.presentation.ui.auth.AuthScreen
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
@ -27,7 +27,7 @@ class MainActivity : ComponentActivity() {
|
|||
DebtPirateTheme {
|
||||
NavHost(navController = navController, startDestination = AuthRoute) {
|
||||
composable<AuthRoute> {
|
||||
AuthScreen( )
|
||||
AuthScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ package ing.bikeshedengineer.debtpirate.data.remote.endpoint
|
|||
import ing.bikeshedengineer.debtpirate.data.remote.model.ApiResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginResponse
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface AuthEndpoint {
|
||||
@POST("auth/login")
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
package ing.bikeshedengineer.debtpirate.data.remote.model
|
||||
|
||||
data class ApiResponse<T>(val data: T?, val err: Any?)
|
||||
data class ApiResponse<T>(val data: T?, val error: String?)
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package ing.bikeshedengineer.debtpirate.data.remote.model.auth
|
||||
|
||||
data class AuthLoginResponse(val userId: Int, val session: AuthLoginTokenData, val auth: AuthLoginTokenData)
|
||||
data class AuthLoginResponse(
|
||||
val userId: Int,
|
||||
val session: AuthLoginTokenData,
|
||||
val auth: AuthLoginTokenData
|
||||
)
|
||||
|
||||
data class AuthLoginTokenData(val token: String, val expiresAt: String)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package ing.bikeshedengineer.debtpirate.data.remote.repository
|
||||
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.ApiResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginResponse
|
||||
|
||||
interface AuthRepository {
|
||||
suspend fun submitLoginRequest(credentials: AuthLoginRequest): Result<AuthLoginResponse>
|
||||
suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse
|
||||
}
|
|
@ -4,13 +4,9 @@ import dagger.Module
|
|||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import ing.bikeshedengineer.debtpirate.BuildConfig
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
|
||||
import ing.bikeshedengineer.debtpirate.domain.repository.AuthRepositoryImpl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
|
|
@ -21,11 +21,12 @@ object HttpClientModule {
|
|||
return Retrofit.Builder()
|
||||
.baseUrl(BuildConfig.API_BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(OkHttpClient.Builder()
|
||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
})
|
||||
.build()
|
||||
.client(
|
||||
OkHttpClient.Builder()
|
||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -4,5 +4,8 @@ import java.time.ZonedDateTime
|
|||
import java.time.format.DateTimeFormatter.ISO_INSTANT
|
||||
|
||||
data class Token(val token: String, val expiresAt: ZonedDateTime) {
|
||||
constructor(token: String, expiresAtStr: String): this(token, ZonedDateTime.parse(expiresAtStr, ISO_INSTANT))
|
||||
constructor(token: String, expiresAtStr: String) : this(
|
||||
token,
|
||||
ZonedDateTime.parse(expiresAtStr, ISO_INSTANT)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ing.bikeshedengineer.debtpirate.domain.repository
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.AuthEndpoint
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.ApiResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
|
||||
|
@ -8,21 +10,24 @@ import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.Retrofit
|
||||
import kotlin.Result
|
||||
|
||||
class AuthRepositoryImpl constructor(private val httpClient: Retrofit): AuthRepository {
|
||||
class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository {
|
||||
private val authEndpoint: AuthEndpoint = this.httpClient.create(AuthEndpoint::class.java)
|
||||
|
||||
override suspend fun submitLoginRequest(credentials: AuthLoginRequest): Result<AuthLoginResponse> {
|
||||
override suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = authEndpoint.submitLoginRequest(credentials)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
return@withContext Result.success(body?.data!!)
|
||||
return@withContext body!!.data!!
|
||||
} else {
|
||||
val error = response.errorBody()
|
||||
return@withContext Result.failure(Throwable(error.toString()))
|
||||
val gson = Gson()
|
||||
val errorType = object : TypeToken<ApiResponse<Unit>>() {}.type
|
||||
val body =
|
||||
gson.fromJson<ApiResponse<Unit>>(response.errorBody()!!.charStream(), errorType)
|
||||
|
||||
throw Throwable(body!!.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ private fun LoginComponent(
|
|||
leadingIcon = { Icon(Icons.Outlined.Person, "person") },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.Words,
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
keyboardType = KeyboardType.Text,
|
||||
imeAction = ImeAction.Next
|
||||
),
|
||||
|
@ -117,7 +117,8 @@ private fun LoginComponent(
|
|||
onSend = { submitLoginRequest() }
|
||||
),
|
||||
onValueChange = onUpdatePassword,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(PaddingValues(top = 4.dp))
|
||||
)
|
||||
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
package ing.bikeshedengineer.debtpirate.presentation.ui.auth
|
||||
|
||||
import android.util.Log
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import ing.bikeshedengineer.debtpirate.PrefsDataStore
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
|
||||
import ing.bikeshedengineer.debtpirate.domain.model.Token
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AuthScreenViewModel @Inject constructor(private val authRepository: AuthRepository) : ViewModel() {
|
||||
class AuthScreenViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
private val prefsStore: DataStore<PrefsDataStore>
|
||||
) : ViewModel() {
|
||||
private val tag: String
|
||||
get() {
|
||||
return javaClass.simpleName
|
||||
|
@ -46,4 +52,24 @@ class AuthScreenViewModel @Inject constructor(private val authRepository: AuthRe
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun storeAuthData(userId: Int, sessionToken: Token, authToken: Token) {
|
||||
viewModelScope.launch {
|
||||
prefsStore.updateData { currentPrefs ->
|
||||
val updatedSessionToken = currentPrefs.sessionToken.toBuilder()
|
||||
.setToken(sessionToken.token)
|
||||
.setExpiresAt(sessionToken.expiresAt.toEpochSecond())
|
||||
|
||||
val updatedAuthToken = currentPrefs.authToken.toBuilder()
|
||||
.setToken(authToken.token)
|
||||
.setExpiresAt(authToken.expiresAt.toEpochSecond())
|
||||
|
||||
currentPrefs.toBuilder()
|
||||
.setUserId(userId)
|
||||
.setSessionToken(updatedSessionToken)
|
||||
.setAuthToken(updatedAuthToken)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.DebtPirate" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<resources>
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.DebtPirate" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<!--Set application-wide security config using base-config tag.-->
|
||||
<base-config cleartextTrafficPermitted="true"/>
|
||||
<base-config cleartextTrafficPermitted="true" />
|
||||
</network-security-config>
|
|
@ -1,8 +1,7 @@
|
|||
package ing.bikeshedengineer.debtpirate
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
|
|
Loading…
Add table
Reference in a new issue