Replace AccountManager with EncryptedSharedPreferences

This commit is contained in:
Kirill Kamakin
2022-04-08 20:00:53 +05:00
parent ba28f7d322
commit 7c081c199a
41 changed files with 243 additions and 722 deletions

View File

@@ -1,19 +0,0 @@
package gq.kirmanak.mealient.ui.addaccount
import android.os.Bundle
import android.os.PersistableBundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.R
@AndroidEntryPoint
class AddAccountActivity : AppCompatActivity() {
private val viewModel by viewModels<AddAccountViewModel>()
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
supportActionBar?.title = getString(R.string.app_name)
}
}

View File

@@ -1,64 +0,0 @@
package gq.kirmanak.mealient.ui.addaccount
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import by.kirich1409.viewbindingdelegate.viewBinding
import dagger.hilt.android.AndroidEntryPoint
import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.data.network.NetworkError
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
import kotlinx.coroutines.launch
import timber.log.Timber
@AndroidEntryPoint
class AddAccountFragment : Fragment(R.layout.fragment_authentication) {
private val binding by viewBinding(FragmentAuthenticationBinding::bind)
private val viewModel by activityViewModels<AddAccountViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
binding.button.setOnClickListener { onLoginClicked() }
}
private fun onLoginClicked(): Unit = with(binding) {
Timber.v("onLoginClicked() called")
val email: String = emailInput.checkIfInputIsEmpty(
inputLayout = emailInputLayout,
lifecycleOwner = viewLifecycleOwner,
stringId = R.string.fragment_authentication_email_input_empty,
) ?: return
val pass: String = passwordInput.checkIfInputIsEmpty(
inputLayout = passwordInputLayout,
lifecycleOwner = viewLifecycleOwner,
stringId = R.string.fragment_authentication_password_input_empty,
trim = false,
) ?: return
button.isClickable = false
viewLifecycleOwner.lifecycleScope.launch {
onAuthenticationResult(viewModel.authenticate(email, pass))
}
}
private fun onAuthenticationResult(result: Result<Unit>) {
Timber.v("onAuthenticationResult() called with: result = $result")
if (result.isSuccess) {
TODO("Implement authentication success")
}
binding.passwordInputLayout.error = when (result.exceptionOrNull()) {
is NetworkError.Unauthorized -> getString(R.string.fragment_authentication_credentials_incorrect)
else -> null
}
binding.button.isClickable = true
}
}

View File

@@ -1,20 +0,0 @@
package gq.kirmanak.mealient.ui.addaccount
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import gq.kirmanak.mealient.data.auth.AuthRepo
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
import timber.log.Timber
@HiltViewModel
class AddAccountViewModel(
private val authRepo: AuthRepo,
) : ViewModel() {
suspend fun authenticate(username: String, password: String) = runCatchingExceptCancel {
Timber.v("authenticate() called with: username = $username, password = $password")
authRepo.authenticate(username, password)
}.onFailure {
Timber.e(it, "authenticate: can't authenticate")
}
}

View File

@@ -5,7 +5,6 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import by.kirich1409.viewbindingdelegate.viewBinding
import dagger.hilt.android.AndroidEntryPoint
@@ -13,8 +12,7 @@ import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.data.network.NetworkError
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
import gq.kirmanak.mealient.extensions.executeOnceOnBackPressed
import kotlinx.coroutines.launch
import gq.kirmanak.mealient.extensions.launchWithViewLifecycle
import timber.log.Timber
@AndroidEntryPoint
@@ -22,12 +20,6 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
private val binding by viewBinding(FragmentAuthenticationBinding::bind)
private val viewModel by activityViewModels<AuthenticationViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
executeOnceOnBackPressed { viewModel.authRequested = false }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
@@ -53,9 +45,7 @@ class AuthenticationFragment : Fragment(R.layout.fragment_authentication) {
) ?: return
button.isClickable = false
viewLifecycleOwner.lifecycleScope.launch {
onAuthenticationResult(viewModel.authenticate(email, pass))
}
launchWithViewLifecycle { onAuthenticationResult(viewModel.authenticate(email, pass)) }
}
private fun onAuthenticationResult(result: Result<Unit>) {

View File

@@ -4,22 +4,19 @@ import timber.log.Timber
enum class AuthenticationState {
AUTHORIZED,
AUTH_REQUESTED,
UNAUTHORIZED,
UNKNOWN;
companion object {
fun determineState(
isLoginRequested: Boolean,
showLoginButton: Boolean,
isAuthorized: Boolean,
): AuthenticationState {
Timber.v("determineState() called with: isLoginRequested = $isLoginRequested, showLoginButton = $showLoginButton, isAuthorized = $isAuthorized")
Timber.v("determineState() called with: showLoginButton = $showLoginButton, isAuthorized = $isAuthorized")
val result = when {
!showLoginButton -> UNKNOWN
isAuthorized -> AUTHORIZED
isLoginRequested -> AUTH_REQUESTED
else -> UNAUTHORIZED
}
Timber.v("determineState() returned: $result")

View File

@@ -18,21 +18,18 @@ class AuthenticationViewModel @Inject constructor(
private val authRepo: AuthRepo,
) : ViewModel() {
private val authRequestsFlow = MutableStateFlow(false)
private val showLoginButtonFlow = MutableStateFlow(false)
private val authenticationStateFlow = combine(
authRequestsFlow,
showLoginButtonFlow,
authRepo.isAuthorizedFlow,
AuthenticationState::determineState
)
val authenticationStateLive: LiveData<AuthenticationState>
get() = authenticationStateFlow.asLiveData()
var authRequested: Boolean by authRequestsFlow::value
var showLoginButton: Boolean by showLoginButtonFlow::value
suspend fun authenticate(username: String, password: String) = runCatchingExceptCancel {
authRepo.authenticate(username, password)
suspend fun authenticate(email: String, password: String) = runCatchingExceptCancel {
authRepo.authenticate(email, password)
}.onFailure {
Timber.e(it, "authenticate: can't authenticate")
}

View File

@@ -11,6 +11,7 @@ import gq.kirmanak.mealient.R
import gq.kirmanak.mealient.data.network.NetworkError
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
import gq.kirmanak.mealient.extensions.launchWithViewLifecycle
import timber.log.Timber
@AndroidEntryPoint
@@ -22,7 +23,6 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")
viewModel.screenState.observe(viewLifecycleOwner, ::updateState)
binding.button.setOnClickListener(::onProceedClick)
}
@@ -33,16 +33,16 @@ class BaseURLFragment : Fragment(R.layout.fragment_base_url) {
lifecycleOwner = viewLifecycleOwner,
stringId = R.string.fragment_baseurl_url_input_empty,
) ?: return
viewModel.saveBaseUrl(url)
launchWithViewLifecycle { onCheckURLResult(viewModel.saveBaseUrl(url)) }
}
private fun updateState(baseURLScreenState: BaseURLScreenState) {
Timber.v("updateState() called with: baseURLScreenState = $baseURLScreenState")
if (baseURLScreenState.navigateNext) {
private fun onCheckURLResult(result: Result<Unit>) {
Timber.v("onCheckURLResult() called with: result = $result")
if (result.isSuccess) {
findNavController().navigate(BaseURLFragmentDirections.actionBaseURLFragmentToRecipesFragment())
return
}
binding.urlInputLayout.error = when (val exception = baseURLScreenState.error) {
binding.urlInputLayout.error = when (val exception = result.exceptionOrNull()) {
is NetworkError.NoServerConnection -> getString(R.string.fragment_base_url_no_connection)
is NetworkError.NotMealie -> getString(R.string.fragment_base_url_unexpected_response)
is NetworkError.MalformedUrl -> {

View File

@@ -1,8 +0,0 @@
package gq.kirmanak.mealient.ui.baseurl
import gq.kirmanak.mealient.data.network.NetworkError
data class BaseURLScreenState(
val error: NetworkError? = null,
val navigateNext: Boolean = false,
)

View File

@@ -1,14 +1,10 @@
package gq.kirmanak.mealient.ui.baseurl
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
import gq.kirmanak.mealient.data.network.NetworkError
import kotlinx.coroutines.launch
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
import timber.log.Timber
import javax.inject.Inject
@@ -18,35 +14,21 @@ class BaseURLViewModel @Inject constructor(
private val versionDataSource: VersionDataSource,
) : ViewModel() {
private val _screenState = MutableLiveData(BaseURLScreenState())
var currentScreenState: BaseURLScreenState
get() = _screenState.value!!
private set(value) {
_screenState.value = value
}
val screenState: LiveData<BaseURLScreenState>
get() = _screenState
fun saveBaseUrl(baseURL: String) {
suspend fun saveBaseUrl(baseURL: String): Result<Unit> {
Timber.v("saveBaseUrl() called with: baseURL = $baseURL")
val hasPrefix = ALLOWED_PREFIXES.any { baseURL.startsWith(it) }
val url = baseURL.takeIf { hasPrefix } ?: WITH_PREFIX_FORMAT.format(baseURL)
viewModelScope.launch { checkBaseURL(url) }
return checkBaseURL(url)
}
private suspend fun checkBaseURL(baseURL: String) {
private suspend fun checkBaseURL(baseURL: String): Result<Unit> {
Timber.v("checkBaseURL() called with: baseURL = $baseURL")
val version = try {
val result = runCatchingExceptCancel {
// If it returns proper version info then it must be a Mealie
versionDataSource.getVersionInfo(baseURL)
} catch (e: NetworkError) {
Timber.e(e, "checkBaseURL: can't get version info")
currentScreenState = BaseURLScreenState(e, false)
return
}
Timber.d("checkBaseURL: version is $version")
baseURLStorage.storeBaseURL(baseURL)
currentScreenState = BaseURLScreenState(null, true)
return result.map { }
}
companion object {

View File

@@ -14,7 +14,6 @@ import gq.kirmanak.mealient.data.recipes.db.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.databinding.FragmentRecipesBinding
import gq.kirmanak.mealient.extensions.collectWithViewLifecycle
import gq.kirmanak.mealient.extensions.refreshRequestFlow
import gq.kirmanak.mealient.ui.auth.AuthenticationState
import gq.kirmanak.mealient.ui.auth.AuthenticationViewModel
import timber.log.Timber
@@ -24,19 +23,6 @@ class RecipesFragment : Fragment(R.layout.fragment_recipes) {
private val viewModel by viewModels<RecipeViewModel>()
private val authViewModel by activityViewModels<AuthenticationViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.v("onCreate() called with: savedInstanceState = $savedInstanceState")
authViewModel.authenticationStateLive.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?) {
super.onViewCreated(view, savedInstanceState)
Timber.v("onViewCreated() called with: view = $view, savedInstanceState = $savedInstanceState")