Implement login/logout functionality
This commit is contained in:
@@ -9,6 +9,9 @@ import com.google.android.material.shape.CornerFamily
|
|||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.databinding.MainActivityBinding
|
import gq.kirmanak.mealient.databinding.MainActivityBinding
|
||||||
|
import gq.kirmanak.mealient.ui.auth.AuthenticationState
|
||||||
|
import gq.kirmanak.mealient.ui.auth.AuthenticationState.AUTHORIZED
|
||||||
|
import gq.kirmanak.mealient.ui.auth.AuthenticationState.UNAUTHORIZED
|
||||||
import gq.kirmanak.mealient.ui.auth.AuthenticationViewModel
|
import gq.kirmanak.mealient.ui.auth.AuthenticationViewModel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -16,7 +19,8 @@ import timber.log.Timber
|
|||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: MainActivityBinding
|
||||||
private val authViewModel by viewModels<AuthenticationViewModel>()
|
private val authViewModel by viewModels<AuthenticationViewModel>()
|
||||||
private var isAuthenticated = false
|
private val authenticationState: AuthenticationState
|
||||||
|
get() = authViewModel.currentAuthenticationState
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -48,32 +52,34 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun listenToAuthStatuses() {
|
private fun listenToAuthStatuses() {
|
||||||
Timber.v("listenToAuthStatuses() called")
|
Timber.v("listenToAuthStatuses() called")
|
||||||
authViewModel.authenticationStatuses().observe(this) {
|
authViewModel.authenticationState.observe(this, ::onAuthStateUpdate)
|
||||||
changeAuthStatus(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeAuthStatus(it: Boolean) {
|
private fun onAuthStateUpdate(authState: AuthenticationState) {
|
||||||
Timber.v("changeAuthStatus() called with: it = $it")
|
Timber.v("onAuthStateUpdate() called with: it = $authState")
|
||||||
if (isAuthenticated == it) return
|
|
||||||
isAuthenticated = it
|
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
Timber.v("onCreateOptionsMenu() called with: menu = $menu")
|
Timber.v("onCreateOptionsMenu() called with: menu = $menu")
|
||||||
menuInflater.inflate(R.menu.main_toolbar, menu)
|
menuInflater.inflate(R.menu.main_toolbar, menu)
|
||||||
menu.findItem(R.id.logout).isVisible = isAuthenticated
|
menu.findItem(R.id.logout).isVisible = authenticationState == AUTHORIZED
|
||||||
|
menu.findItem(R.id.login).isVisible = authenticationState == UNAUTHORIZED
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
Timber.v("onOptionsItemSelected() called with: item = $item")
|
Timber.v("onOptionsItemSelected() called with: item = $item")
|
||||||
val result = if (item.itemId == R.id.logout) {
|
val result = when (item.itemId) {
|
||||||
|
R.id.logout -> {
|
||||||
authViewModel.logout()
|
authViewModel.logout()
|
||||||
true
|
true
|
||||||
} else {
|
}
|
||||||
super.onOptionsItemSelected(item)
|
R.id.login -> {
|
||||||
|
authViewModel.login()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
|
|
||||||
interface AuthRepo {
|
interface AuthRepo {
|
||||||
|
|
||||||
|
val isAuthorizedFlow: Flow<Boolean>
|
||||||
|
|
||||||
suspend fun authenticate(username: String, password: String)
|
suspend fun authenticate(username: String, password: String)
|
||||||
|
|
||||||
suspend fun getAuthHeader(): String?
|
suspend fun getAuthHeader(): String?
|
||||||
|
|
||||||
suspend fun requireAuthHeader(): String
|
suspend fun requireAuthHeader(): String
|
||||||
|
|
||||||
fun authenticationStatuses(): Flow<Boolean>
|
|
||||||
|
|
||||||
suspend fun logout()
|
suspend fun logout()
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,12 @@ package gq.kirmanak.mealient.data.auth
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AuthStorage {
|
interface AuthStorage {
|
||||||
|
|
||||||
|
val authHeaderFlow: Flow<String?>
|
||||||
|
|
||||||
suspend fun storeAuthData(authHeader: String)
|
suspend fun storeAuthData(authHeader: String)
|
||||||
|
|
||||||
suspend fun getAuthHeader(): String?
|
suspend fun getAuthHeader(): String?
|
||||||
|
|
||||||
fun authHeaderObservable(): Flow<String?>
|
|
||||||
|
|
||||||
suspend fun clearAuthData()
|
suspend fun clearAuthData()
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,9 @@ class AuthRepoImpl @Inject constructor(
|
|||||||
private val storage: AuthStorage,
|
private val storage: AuthStorage,
|
||||||
) : AuthRepo {
|
) : AuthRepo {
|
||||||
|
|
||||||
|
override val isAuthorizedFlow: Flow<Boolean>
|
||||||
|
get() = storage.authHeaderFlow.map { it != null }
|
||||||
|
|
||||||
override suspend fun authenticate(username: String, password: String) {
|
override suspend fun authenticate(username: String, password: String) {
|
||||||
Timber.v("authenticate() called with: username = $username, password = $password")
|
Timber.v("authenticate() called with: username = $username, password = $password")
|
||||||
val accessToken = dataSource.authenticate(username, password)
|
val accessToken = dataSource.authenticate(username, password)
|
||||||
@@ -27,11 +30,6 @@ class AuthRepoImpl @Inject constructor(
|
|||||||
override suspend fun requireAuthHeader(): String =
|
override suspend fun requireAuthHeader(): String =
|
||||||
checkNotNull(getAuthHeader()) { "Auth header is null when it was required" }
|
checkNotNull(getAuthHeader()) { "Auth header is null when it was required" }
|
||||||
|
|
||||||
override fun authenticationStatuses(): Flow<Boolean> {
|
|
||||||
Timber.v("authenticationStatuses() called")
|
|
||||||
return storage.authHeaderObservable().map { it != null }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun logout() {
|
override suspend fun logout() {
|
||||||
Timber.v("logout() called")
|
Timber.v("logout() called")
|
||||||
storage.clearAuthData()
|
storage.clearAuthData()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.data.auth.impl
|
package gq.kirmanak.mealient.data.auth.impl
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import gq.kirmanak.mealient.data.auth.AuthStorage
|
import gq.kirmanak.mealient.data.auth.AuthStorage
|
||||||
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -12,7 +13,10 @@ class AuthStorageImpl @Inject constructor(
|
|||||||
private val preferencesStorage: PreferencesStorage,
|
private val preferencesStorage: PreferencesStorage,
|
||||||
) : AuthStorage {
|
) : AuthStorage {
|
||||||
|
|
||||||
private val authHeaderKey by preferencesStorage::authHeaderKey
|
private val authHeaderKey: Preferences.Key<String>
|
||||||
|
get() = preferencesStorage.authHeaderKey
|
||||||
|
override val authHeaderFlow: Flow<String?>
|
||||||
|
get() = preferencesStorage.valueUpdates(authHeaderKey)
|
||||||
|
|
||||||
override suspend fun storeAuthData(authHeader: String) {
|
override suspend fun storeAuthData(authHeader: String) {
|
||||||
Timber.v("storeAuthData() called with: authHeader = $authHeader")
|
Timber.v("storeAuthData() called with: authHeader = $authHeader")
|
||||||
@@ -26,11 +30,6 @@ class AuthStorageImpl @Inject constructor(
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun authHeaderObservable(): Flow<String?> {
|
|
||||||
Timber.v("authHeaderObservable() called")
|
|
||||||
return preferencesStorage.valueUpdates(authHeaderKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun clearAuthData() {
|
override suspend fun clearAuthData() {
|
||||||
Timber.v("clearAuthData() called")
|
Timber.v("clearAuthData() called")
|
||||||
preferencesStorage.removeValues(authHeaderKey)
|
preferencesStorage.removeValues(authHeaderKey)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.data.baseurl
|
package gq.kirmanak.mealient.data.baseurl
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -9,7 +10,8 @@ class BaseURLStorageImpl @Inject constructor(
|
|||||||
private val preferencesStorage: PreferencesStorage,
|
private val preferencesStorage: PreferencesStorage,
|
||||||
) : BaseURLStorage {
|
) : BaseURLStorage {
|
||||||
|
|
||||||
private val baseUrlKey by preferencesStorage::baseUrlKey
|
private val baseUrlKey: Preferences.Key<String>
|
||||||
|
get() = preferencesStorage.baseUrlKey
|
||||||
|
|
||||||
override suspend fun getBaseURL(): String? = preferencesStorage.getValue(baseUrlKey)
|
override suspend fun getBaseURL(): String? = preferencesStorage.getValue(baseUrlKey)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package gq.kirmanak.mealient.data.disclaimer
|
package gq.kirmanak.mealient.data.disclaimer
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface DisclaimerStorage {
|
interface DisclaimerStorage {
|
||||||
|
|
||||||
|
val isDisclaimerAcceptedFlow: Flow<Boolean>
|
||||||
|
|
||||||
suspend fun isDisclaimerAccepted(): Boolean
|
suspend fun isDisclaimerAccepted(): Boolean
|
||||||
|
|
||||||
suspend fun acceptDisclaimer()
|
suspend fun acceptDisclaimer()
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package gq.kirmanak.mealient.data.disclaimer
|
package gq.kirmanak.mealient.data.disclaimer
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -10,7 +13,10 @@ class DisclaimerStorageImpl @Inject constructor(
|
|||||||
private val preferencesStorage: PreferencesStorage,
|
private val preferencesStorage: PreferencesStorage,
|
||||||
) : DisclaimerStorage {
|
) : DisclaimerStorage {
|
||||||
|
|
||||||
private val isDisclaimerAcceptedKey by preferencesStorage::isDisclaimerAcceptedKey
|
private val isDisclaimerAcceptedKey: Preferences.Key<Boolean>
|
||||||
|
get() = preferencesStorage.isDisclaimerAcceptedKey
|
||||||
|
override val isDisclaimerAcceptedFlow: Flow<Boolean>
|
||||||
|
get() = preferencesStorage.valueUpdates(isDisclaimerAcceptedKey).map { it == true }
|
||||||
|
|
||||||
override suspend fun isDisclaimerAccepted(): Boolean {
|
override suspend fun isDisclaimerAccepted(): Boolean {
|
||||||
Timber.v("isDisclaimerAccepted() called")
|
Timber.v("isDisclaimerAccepted() called")
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
@@ -19,22 +19,15 @@ import timber.log.Timber
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
|
class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
|
||||||
private val binding by viewBinding(FragmentAuthenticationBinding::bind)
|
private val binding by viewBinding(FragmentAuthenticationBinding::bind)
|
||||||
private val viewModel by viewModels<AuthenticationViewModel>()
|
private val viewModel by activityViewModels<AuthenticationViewModel>()
|
||||||
|
|
||||||
private val authStatuses by lazy { viewModel.authenticationStatuses() }
|
private val authStatuses: LiveData<AuthenticationState>
|
||||||
private val authStatusObserver = Observer<Boolean> { onAuthStatusChange(it) }
|
get() = viewModel.authenticationState
|
||||||
private fun onAuthStatusChange(isAuthenticated: Boolean) {
|
|
||||||
Timber.v("onAuthStatusChange() called with: isAuthenticated = $isAuthenticated")
|
|
||||||
if (isAuthenticated) {
|
|
||||||
authStatuses.removeObserver(authStatusObserver)
|
|
||||||
navigateToRecipes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
|
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
|
||||||
authStatuses.observe(this, authStatusObserver)
|
authStatuses.observe(this, ::onAuthStatusChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -45,9 +38,11 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
|
|||||||
getString(R.string.app_name)
|
getString(R.string.app_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToRecipes() {
|
private fun onAuthStatusChange(isAuthenticated: AuthenticationState) {
|
||||||
Timber.v("navigateToRecipes() called")
|
Timber.v("onAuthStatusChange() called with: isAuthenticated = $isAuthenticated")
|
||||||
findNavController().navigate(AuthenticationFragmentDirections.actionAuthenticationFragmentToRecipesFragment())
|
if (isAuthenticated == AuthenticationState.AUTHORIZED) {
|
||||||
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onLoginClicked(): Unit = with(binding) {
|
private fun onLoginClicked(): Unit = with(binding) {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package gq.kirmanak.mealient.ui.auth
|
||||||
|
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
enum class AuthenticationState {
|
||||||
|
AUTHORIZED,
|
||||||
|
AUTH_REQUESTED,
|
||||||
|
UNAUTHORIZED;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun determineState(
|
||||||
|
isLoginRequested: Boolean,
|
||||||
|
isAuthorized: Boolean,
|
||||||
|
): AuthenticationState {
|
||||||
|
Timber.v("determineState() called with: isLoginRequested = $isLoginRequested, isAuthorized = $isAuthorized")
|
||||||
|
val result = when {
|
||||||
|
isAuthorized -> AUTHORIZED
|
||||||
|
isLoginRequested -> AUTH_REQUESTED
|
||||||
|
else -> UNAUTHORIZED
|
||||||
|
}
|
||||||
|
Timber.v("determineState() returned: $result")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,8 @@ package gq.kirmanak.mealient.ui.auth
|
|||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -11,9 +12,16 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AuthenticationViewModel @Inject constructor(
|
class AuthenticationViewModel @Inject constructor(
|
||||||
private val authRepo: AuthRepo,
|
private val authRepo: AuthRepo,
|
||||||
private val recipeRepo: RecipeRepo
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val loginRequestsFlow = MutableStateFlow(false)
|
||||||
|
val authenticationState: LiveData<AuthenticationState> = loginRequestsFlow.combine(
|
||||||
|
flow = authRepo.isAuthorizedFlow,
|
||||||
|
transform = AuthenticationState::determineState
|
||||||
|
).asLiveData()
|
||||||
|
val currentAuthenticationState: AuthenticationState
|
||||||
|
get() = checkNotNull(authenticationState.value) { "Auth state flow mustn't be null" }
|
||||||
|
|
||||||
fun authenticate(username: String, password: String): LiveData<Result<Unit>> {
|
fun authenticate(username: String, password: String): LiveData<Result<Unit>> {
|
||||||
Timber.v("authenticate() called with: username = $username, password = $password")
|
Timber.v("authenticate() called with: username = $username, password = $password")
|
||||||
val result = MutableLiveData<Result<Unit>>()
|
val result = MutableLiveData<Result<Unit>>()
|
||||||
@@ -31,16 +39,16 @@ class AuthenticationViewModel @Inject constructor(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun authenticationStatuses(): LiveData<Boolean> {
|
|
||||||
Timber.v("authenticationStatuses() called")
|
|
||||||
return authRepo.authenticationStatuses().asLiveData()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun logout() {
|
fun logout() {
|
||||||
Timber.v("logout() called")
|
Timber.v("logout() called")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
loginRequestsFlow.emit(false)
|
||||||
authRepo.logout()
|
authRepo.logout()
|
||||||
recipeRepo.clearLocalData()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun login() {
|
||||||
|
Timber.v("login() called")
|
||||||
|
viewModelScope.launch { loginRequestsFlow.emit(true) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,12 +4,14 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.data.network.NetworkError
|
import gq.kirmanak.mealient.data.network.NetworkError
|
||||||
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
|
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
|
||||||
|
import gq.kirmanak.mealient.ui.checkIfInputIsEmpty
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -22,9 +24,15 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
|
||||||
viewModel.screenState.observe(viewLifecycleOwner, ::updateState)
|
viewModel.screenState.observe(viewLifecycleOwner, ::updateState)
|
||||||
binding.button.setOnClickListener {
|
binding.button.setOnClickListener(::onProceedClick)
|
||||||
viewModel.saveBaseUrl(binding.urlInput.text.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onProceedClick(view: View) {
|
||||||
|
Timber.v("onProceedClick() called with: view = $view")
|
||||||
|
val url = binding.urlInput.checkIfInputIsEmpty(binding.urlInputLayout, lifecycleScope) {
|
||||||
|
getString(R.string.fragment_baseurl_url_input_empty)
|
||||||
|
} ?: return
|
||||||
|
viewModel.saveBaseUrl(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateState(baseURLScreenState: BaseURLScreenState) {
|
private fun updateState(baseURLScreenState: BaseURLScreenState) {
|
||||||
|
|||||||
@@ -24,15 +24,20 @@ class BaseURLViewModel @Inject constructor(
|
|||||||
private set(value) {
|
private set(value) {
|
||||||
_screenState.value = value
|
_screenState.value = value
|
||||||
}
|
}
|
||||||
val screenState: LiveData<BaseURLScreenState> by ::_screenState
|
val screenState: LiveData<BaseURLScreenState>
|
||||||
|
get() = _screenState
|
||||||
|
|
||||||
fun saveBaseUrl(baseURL: String) {
|
fun saveBaseUrl(baseURL: String) {
|
||||||
Timber.v("saveBaseUrl() called with: baseURL = $baseURL")
|
Timber.v("saveBaseUrl() called with: baseURL = $baseURL")
|
||||||
viewModelScope.launch { checkBaseURL(baseURL) }
|
val hasPrefix = ALLOWED_PREFIXES.any { baseURL.startsWith(it) }
|
||||||
|
val url = baseURL.takeIf { hasPrefix } ?: WITH_PREFIX_FORMAT.format(baseURL)
|
||||||
|
viewModelScope.launch { checkBaseURL(url) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun checkBaseURL(baseURL: String) {
|
private suspend fun checkBaseURL(baseURL: String) {
|
||||||
|
Timber.v("checkBaseURL() called with: baseURL = $baseURL")
|
||||||
val version = try {
|
val version = try {
|
||||||
|
// If it returns proper version info then it must be a Mealie
|
||||||
versionDataSource.getVersionInfo(baseURL)
|
versionDataSource.getVersionInfo(baseURL)
|
||||||
} catch (e: NetworkError) {
|
} catch (e: NetworkError) {
|
||||||
Timber.e(e, "checkBaseURL: can't get version info")
|
Timber.e(e, "checkBaseURL: can't get version info")
|
||||||
@@ -43,4 +48,9 @@ class BaseURLViewModel @Inject constructor(
|
|||||||
baseURLStorage.storeBaseURL(baseURL)
|
baseURLStorage.storeBaseURL(baseURL)
|
||||||
currentScreenState = BaseURLScreenState(null, true)
|
currentScreenState = BaseURLScreenState(null, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val ALLOWED_PREFIXES = listOf("http://", "https://")
|
||||||
|
private const val WITH_PREFIX_FORMAT = "https://%s"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -20,16 +20,12 @@ class DisclaimerFragment : Fragment(R.layout.fragment_disclaimer) {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
|
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
|
||||||
listenToAcceptStatus()
|
viewModel.isAccepted.observe(this, ::onAcceptStateChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun listenToAcceptStatus() {
|
private fun onAcceptStateChange(isAccepted: Boolean) {
|
||||||
Timber.v("listenToAcceptStatus() called")
|
Timber.v("onAcceptStateChange() called with: isAccepted = $isAccepted")
|
||||||
viewModel.isAccepted.observe(this) {
|
if (isAccepted) navigateNext()
|
||||||
Timber.d("listenToAcceptStatus: new status = $it")
|
|
||||||
if (it) navigateNext()
|
|
||||||
}
|
|
||||||
viewModel.checkIsAccepted()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateNext() {
|
private fun navigateNext() {
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package gq.kirmanak.mealient.ui.disclaimer
|
package gq.kirmanak.mealient.ui.disclaimer
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.*
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -21,25 +18,15 @@ import javax.inject.Inject
|
|||||||
class DisclaimerViewModel @Inject constructor(
|
class DisclaimerViewModel @Inject constructor(
|
||||||
private val disclaimerStorage: DisclaimerStorage
|
private val disclaimerStorage: DisclaimerStorage
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _isAccepted = MutableLiveData(false)
|
val isAccepted: LiveData<Boolean>
|
||||||
val isAccepted: LiveData<Boolean> = _isAccepted
|
get() = disclaimerStorage.isDisclaimerAcceptedFlow.asLiveData()
|
||||||
|
|
||||||
private val _okayCountDown = MutableLiveData(FULL_COUNT_DOWN_SEC)
|
private val _okayCountDown = MutableLiveData(FULL_COUNT_DOWN_SEC)
|
||||||
val okayCountDown: LiveData<Int> = _okayCountDown
|
val okayCountDown: LiveData<Int> = _okayCountDown
|
||||||
|
|
||||||
fun checkIsAccepted() {
|
|
||||||
Timber.v("checkIsAccepted() called")
|
|
||||||
viewModelScope.launch {
|
|
||||||
_isAccepted.value = disclaimerStorage.isDisclaimerAccepted()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun acceptDisclaimer() {
|
fun acceptDisclaimer() {
|
||||||
Timber.v("acceptDisclaimer() called")
|
Timber.v("acceptDisclaimer() called")
|
||||||
viewModelScope.launch {
|
viewModelScope.launch { disclaimerStorage.acceptDisclaimer() }
|
||||||
disclaimerStorage.acceptDisclaimer()
|
|
||||||
_isAccepted.value = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startCountDown() {
|
fun startCountDown() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
@@ -12,6 +13,8 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.databinding.FragmentRecipesBinding
|
import gq.kirmanak.mealient.databinding.FragmentRecipesBinding
|
||||||
|
import gq.kirmanak.mealient.ui.auth.AuthenticationState
|
||||||
|
import gq.kirmanak.mealient.ui.auth.AuthenticationViewModel
|
||||||
import gq.kirmanak.mealient.ui.refreshesLiveData
|
import gq.kirmanak.mealient.ui.refreshesLiveData
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -20,6 +23,19 @@ import timber.log.Timber
|
|||||||
class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
class RecipesFragment : Fragment(R.layout.fragment_recipes) {
|
||||||
private val binding by viewBinding(FragmentRecipesBinding::bind)
|
private val binding by viewBinding(FragmentRecipesBinding::bind)
|
||||||
private val viewModel by viewModels<RecipeViewModel>()
|
private val viewModel by viewModels<RecipeViewModel>()
|
||||||
|
private val authViewModel by activityViewModels<AuthenticationViewModel>()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
authViewModel.authenticationState.observe(this, ::onAuthStateChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onAuthStateChange(authenticationState: AuthenticationState) {
|
||||||
|
Timber.v("onAuthStateChange() called with: authenticationState = $authenticationState")
|
||||||
|
if (authenticationState == AuthenticationState.AUTH_REQUESTED) {
|
||||||
|
findNavController().navigate(RecipesFragmentDirections.actionRecipesFragmentToAuthenticationFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ constructor(
|
|||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _recipeInfo = MutableLiveData<FullRecipeInfo>()
|
private val _recipeInfo = MutableLiveData<FullRecipeInfo>()
|
||||||
val recipeInfo: LiveData<FullRecipeInfo> by ::_recipeInfo
|
val recipeInfo: LiveData<FullRecipeInfo>
|
||||||
|
get() = _recipeInfo
|
||||||
private val _listsVisibility = MutableLiveData(RecipeInfoListsVisibility())
|
private val _listsVisibility = MutableLiveData(RecipeInfoListsVisibility())
|
||||||
val listsVisibility: LiveData<RecipeInfoListsVisibility> by ::_listsVisibility
|
val listsVisibility: LiveData<RecipeInfoListsVisibility>
|
||||||
|
get() = _listsVisibility
|
||||||
|
|
||||||
fun loadRecipeImage(view: ImageView, recipeSlug: String) {
|
fun loadRecipeImage(view: ImageView, recipeSlug: String) {
|
||||||
Timber.v("loadRecipeImage() called with: view = $view, recipeSlug = $recipeSlug")
|
Timber.v("loadRecipeImage() called with: view = $view, recipeSlug = $recipeSlug")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.view.View
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.navigation.NavDirections
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
@@ -19,10 +20,12 @@ class SplashFragment : Fragment(R.layout.fragment_splash) {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
|
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
|
||||||
viewModel.nextDestination.observe(this) {
|
viewModel.nextDestination.observe(this, ::onNextDestination)
|
||||||
Timber.d("onCreate: next destination $it")
|
|
||||||
findNavController().navigate(it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onNextDestination(navDirections: NavDirections) {
|
||||||
|
Timber.v("onNextDestination() called with: navDirections = $navDirections")
|
||||||
|
findNavController().navigate(navDirections)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/login"
|
||||||
|
android:contentDescription="@string/menu_main_toolbar_content_description_login"
|
||||||
|
android:title="@string/menu_main_toolbar_login"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/logout"
|
android:id="@+id/logout"
|
||||||
android:contentDescription="@string/menu_main_toolbar_content_description_logout"
|
android:contentDescription="@string/menu_main_toolbar_content_description_logout"
|
||||||
|
|||||||
@@ -9,13 +9,7 @@
|
|||||||
android:id="@+id/authenticationFragment"
|
android:id="@+id/authenticationFragment"
|
||||||
android:name="gq.kirmanak.mealient.ui.auth.AuthenticationFragment"
|
android:name="gq.kirmanak.mealient.ui.auth.AuthenticationFragment"
|
||||||
android:label="AuthenticationFragment"
|
android:label="AuthenticationFragment"
|
||||||
tools:layout="@layout/fragment_authentication">
|
tools:layout="@layout/fragment_authentication" />
|
||||||
<action
|
|
||||||
android:id="@+id/action_authenticationFragment_to_recipesFragment"
|
|
||||||
app:destination="@id/recipesFragment"
|
|
||||||
app:popUpTo="@id/nav_graph"
|
|
||||||
app:popUpToInclusive="true" />
|
|
||||||
</fragment>
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/recipesFragment"
|
android:id="@+id/recipesFragment"
|
||||||
android:name="gq.kirmanak.mealient.ui.recipes.RecipesFragment"
|
android:name="gq.kirmanak.mealient.ui.recipes.RecipesFragment"
|
||||||
@@ -23,9 +17,7 @@
|
|||||||
tools:layout="@layout/fragment_recipes">
|
tools:layout="@layout/fragment_recipes">
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_recipesFragment_to_authenticationFragment"
|
android:id="@+id/action_recipesFragment_to_authenticationFragment"
|
||||||
app:destination="@id/authenticationFragment"
|
app:destination="@id/authenticationFragment" />
|
||||||
app:popUpTo="@id/nav_graph"
|
|
||||||
app:popUpToInclusive="true" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_recipesFragment_to_recipeInfoFragment"
|
android:id="@+id/action_recipesFragment_to_recipeInfoFragment"
|
||||||
app:destination="@id/recipeInfoFragment" />
|
app:destination="@id/recipeInfoFragment" />
|
||||||
@@ -58,11 +50,6 @@
|
|||||||
android:name="gq.kirmanak.mealient.ui.splash.SplashFragment"
|
android:name="gq.kirmanak.mealient.ui.splash.SplashFragment"
|
||||||
android:label="fragment_splash"
|
android:label="fragment_splash"
|
||||||
tools:layout="@layout/fragment_splash">
|
tools:layout="@layout/fragment_splash">
|
||||||
<action
|
|
||||||
android:id="@+id/action_splashFragment_to_authenticationFragment"
|
|
||||||
app:destination="@id/authenticationFragment"
|
|
||||||
app:popUpTo="@id/nav_graph"
|
|
||||||
app:popUpToInclusive="true" />
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_splashFragment_to_disclaimerFragment"
|
android:id="@+id/action_splashFragment_to_disclaimerFragment"
|
||||||
app:destination="@id/disclaimerFragment"
|
app:destination="@id/disclaimerFragment"
|
||||||
|
|||||||
@@ -14,11 +14,12 @@
|
|||||||
<string name="fragment_disclaimer_main_text">Этот проект разрабатывается независимо от основного проекта Meale. Он не связан с разработчиками Mealie. О любых проблемах следует писать в репозиторий Mealient, НЕ в репозиторий Mealie.</string>
|
<string name="fragment_disclaimer_main_text">Этот проект разрабатывается независимо от основного проекта Meale. Он не связан с разработчиками Mealie. О любых проблемах следует писать в репозиторий Mealient, НЕ в репозиторий Mealie.</string>
|
||||||
<string name="fragment_authentication_email_input_empty">E-mail не может быть пустым</string>
|
<string name="fragment_authentication_email_input_empty">E-mail не может быть пустым</string>
|
||||||
<string name="fragment_authentication_password_input_empty">Пароль не может быть пустым</string>
|
<string name="fragment_authentication_password_input_empty">Пароль не может быть пустым</string>
|
||||||
<string name="fragment_authentication_url_input_empty">URL не может быть пустым</string>
|
<string name="fragment_baseurl_url_input_empty">URL не может быть пустым</string>
|
||||||
<string name="fragment_authentication_credentials_incorrect">E-mail или пароль не подходит.</string>
|
<string name="fragment_authentication_credentials_incorrect">E-mail или пароль не подходит.</string>
|
||||||
<string name="fragment_base_url_no_connection">Ошибка подключения, проверьте адрес.</string>
|
<string name="fragment_base_url_no_connection">Ошибка подключения, проверьте адрес.</string>
|
||||||
<string name="fragment_base_url_unexpected_response">Неожиданный ответ. Это Mealie?</string>
|
<string name="fragment_base_url_unexpected_response">Неожиданный ответ. Это Mealie?</string>
|
||||||
<string name="fragment_authentication_unknown_error">Что-то пошло не так, попробуйте еще раз.</string>
|
<string name="fragment_authentication_unknown_error">Что-то пошло не так, попробуйте еще раз.</string>
|
||||||
<string name="fragment_base_url_malformed_url">Проверьте формат URL: %s</string>
|
<string name="fragment_base_url_malformed_url">Проверьте формат URL: %s</string>
|
||||||
<string name="fragment_base_url_save">Продолжить</string>
|
<string name="fragment_base_url_save">Продолжить</string>
|
||||||
|
<string name="menu_main_toolbar_login">Войти</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<string name="view_holder_recipe_instructions_step">Step: %d</string>
|
<string name="view_holder_recipe_instructions_step">Step: %d</string>
|
||||||
<string name="fragment_authentication_email_input_empty">E-mail can\'t be empty</string>
|
<string name="fragment_authentication_email_input_empty">E-mail can\'t be empty</string>
|
||||||
<string name="fragment_authentication_password_input_empty">Password can\'t be empty</string>
|
<string name="fragment_authentication_password_input_empty">Password can\'t be empty</string>
|
||||||
<string name="fragment_authentication_url_input_empty">URL can\'t be empty</string>
|
<string name="fragment_baseurl_url_input_empty">URL can\'t be empty</string>
|
||||||
<string name="fragment_authentication_credentials_incorrect">E-mail or password is incorrect.</string>
|
<string name="fragment_authentication_credentials_incorrect">E-mail or password is incorrect.</string>
|
||||||
<string name="fragment_base_url_no_connection">Can\'t connect, check address.</string>
|
<string name="fragment_base_url_no_connection">Can\'t connect, check address.</string>
|
||||||
<string name="fragment_base_url_unexpected_response">Unexpected response. Is it Mealie?</string>
|
<string name="fragment_base_url_unexpected_response">Unexpected response. Is it Mealie?</string>
|
||||||
@@ -24,4 +24,6 @@
|
|||||||
<string name="fragment_base_url_malformed_url">Check URL format: %s</string>
|
<string name="fragment_base_url_malformed_url">Check URL format: %s</string>
|
||||||
<string name="fragment_base_url_save">Proceed</string>
|
<string name="fragment_base_url_save">Proceed</string>
|
||||||
<string name="fragment_base_url_unknown_error" translatable="false">@string/fragment_authentication_unknown_error</string>
|
<string name="fragment_base_url_unknown_error" translatable="false">@string/fragment_authentication_unknown_error</string>
|
||||||
|
<string name="menu_main_toolbar_content_description_login" translatable="false">@string/menu_main_toolbar_login</string>
|
||||||
|
<string name="menu_main_toolbar_login">Login</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -39,14 +39,14 @@ class AuthRepoImplTest : RobolectricTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when not authenticated then first auth status is false`() = runTest {
|
fun `when not authenticated then first auth status is false`() = runTest {
|
||||||
coEvery { storage.authHeaderObservable() } returns flowOf(null)
|
coEvery { storage.authHeaderFlow } returns flowOf(null)
|
||||||
assertThat(subject.authenticationStatuses().first()).isFalse()
|
assertThat(subject.isAuthorizedFlow.first()).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when authenticated then first auth status is true`() = runTest {
|
fun `when authenticated then first auth status is true`() = runTest {
|
||||||
coEvery { storage.authHeaderObservable() } returns flowOf(TEST_AUTH_HEADER)
|
coEvery { storage.authHeaderFlow } returns flowOf(TEST_AUTH_HEADER)
|
||||||
assertThat(subject.authenticationStatuses().first()).isTrue()
|
assertThat(subject.isAuthorizedFlow.first()).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = Unauthorized::class)
|
@Test(expected = Unauthorized::class)
|
||||||
|
|||||||
@@ -35,20 +35,20 @@ class AuthStorageImplTest : HiltRobolectricTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when didn't store auth data then first token is null`() = runTest {
|
fun `when didn't store auth data then first token is null`() = runTest {
|
||||||
assertThat(subject.authHeaderObservable().first()).isNull()
|
assertThat(subject.authHeaderFlow.first()).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when stored auth data then first token is correct`() = runTest {
|
fun `when stored auth data then first token is correct`() = runTest {
|
||||||
subject.storeAuthData(TEST_AUTH_HEADER)
|
subject.storeAuthData(TEST_AUTH_HEADER)
|
||||||
assertThat(subject.authHeaderObservable().first()).isEqualTo(TEST_AUTH_HEADER)
|
assertThat(subject.authHeaderFlow.first()).isEqualTo(TEST_AUTH_HEADER)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when clearAuthData then first token is null`() = runTest {
|
fun `when clearAuthData then first token is null`() = runTest {
|
||||||
subject.storeAuthData(TEST_AUTH_HEADER)
|
subject.storeAuthData(TEST_AUTH_HEADER)
|
||||||
subject.clearAuthData()
|
subject.clearAuthData()
|
||||||
assertThat(subject.authHeaderObservable().first()).isNull()
|
assertThat(subject.authHeaderFlow.first()).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user