diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginFragment.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginFragment.kt index 06c3afe..d7b8d0b 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginFragment.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginFragment.kt @@ -7,6 +7,10 @@ import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.ContentBinding import ru.otus.coroutineshomework.databinding.FragmentLoginBinding import ru.otus.coroutineshomework.databinding.LoadingBinding @@ -43,12 +47,16 @@ class LoginFragment : Fragment() { setupLogin() setupContent() - loginViewModel.state.observe(viewLifecycleOwner) { - when(it) { - is LoginViewState.Login -> showLogin(it) - LoginViewState.LoggingIn -> showLoggingIn() - is LoginViewState.Content -> showContent(it) - LoginViewState.LoggingOut -> showLoggingOut() + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + loginViewModel.state.collect { state -> + when(state) { + is LoginViewState.Login -> showLogin(state) + LoginViewState.LoggingIn -> showLoggingIn() + is LoginViewState.Content -> showContent(state) + LoginViewState.LoggingOut -> showLoggingOut() + } + } } } } diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt index 5fae38a..b11a3a8 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/login/LoginViewModel.kt @@ -1,13 +1,24 @@ package ru.otus.coroutineshomework.ui.login -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import ru.otus.coroutineshomework.ui.login.data.Credentials class LoginViewModel : ViewModel() { - private val _state = MutableLiveData(LoginViewState.Login()) - val state: LiveData = _state + private val _state = MutableStateFlow(LoginViewState.Login()) + val state: StateFlow = _state.asStateFlow() + private val loginApi = LoginApi() /** * Login to the network @@ -15,13 +26,35 @@ class LoginViewModel : ViewModel() { * @param password user password */ fun login(name: String, password: String) { - // TODO: Implement login + viewModelScope.launch { + loginFlow(name, password).collect { viewState -> + _state.value = viewState + } + } } + private fun loginFlow(name: String, password: String): Flow = flow { + emit(LoginViewState.LoggingIn) + val user = loginApi.login(Credentials(name, password)) + emit(LoginViewState.Content(user)) + }.catch { e -> + emit(LoginViewState.Login(error = e as? Exception)) + }.flowOn(Dispatchers.IO) + /** * Logout from the network */ fun logout() { - // TODO: Implement logout + viewModelScope.launch { + _state.value = LoginViewState.LoggingOut + try { + withContext(Dispatchers.IO) { + loginApi.logout() + } + _state.value = LoginViewState.Login() + } catch (e: Exception) { + _state.value = LoginViewState.Login(error = e) + } + } } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/network/NetworkViewModel.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/network/NetworkViewModel.kt index f006e03..67e63e3 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/network/NetworkViewModel.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/network/NetworkViewModel.kt @@ -4,8 +4,12 @@ import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.random.Random @@ -18,7 +22,29 @@ class NetworkViewModel : ViewModel() { val result: LiveData = _result fun startTest(numberOfThreads: Int) { - // TODO: Implement the logic + viewModelScope.launch { + _running.value = true + _result.value = null + + try { + val tasks = (1..numberOfThreads).map { + async { + emulateBlockingNetworkRequest() + } + } + + val results = tasks.awaitAll() + + val successTimes = results.mapNotNull { it.getOrNull() } + if (successTimes.isNotEmpty()) { + _result.value = successTimes.average().toLong() + } + } catch (e: Exception) { + Log.e(TAG, "Test failed", e) + } finally { + _running.value = false + } + } } private companion object { diff --git a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt index 1b7c0f1..95186e1 100644 --- a/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt +++ b/app/src/main/kotlin/ru/otus/coroutineshomework/ui/timer/TimerFragment.kt @@ -5,9 +5,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding @@ -21,9 +24,7 @@ class TimerFragment : Fragment() { private var _binding: FragmentTimerBinding? = null private val binding get() = _binding!! - private var time: Duration by Delegates.observable(Duration.ZERO) { _, _, newValue -> - binding.time.text = newValue.toDisplayString() - } + private val timeFlow = MutableStateFlow(Duration.ZERO) private var started by Delegates.observable(false) { _, _, newValue -> setButtonsState(newValue) @@ -53,33 +54,47 @@ class TimerFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) savedInstanceState?.let { - time = it.getLong(TIME).milliseconds + val savedMs = it.getLong(TIME) + timeFlow.value = savedMs.milliseconds started = it.getBoolean(STARTED) } setButtonsState(started) - with(binding) { - time.text = this@TimerFragment.time.toDisplayString() - btnStart.setOnClickListener { - started = true - } - btnStop.setOnClickListener { - started = false + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + timeFlow.collect { duration -> + binding.time.text = duration.toDisplayString() + } } } + with(binding) { + btnStart.setOnClickListener { started = true } + btnStop.setOnClickListener { started = false } + } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putLong(TIME, time.inWholeMilliseconds) + outState.putLong(TIME, timeFlow.value.inWholeMilliseconds) outState.putBoolean(STARTED, started) } + private var timerJob: Job? = null + private fun startTimer() { - // TODO: Start timer + timerJob?.cancel() + timerJob = viewLifecycleOwner.lifecycleScope.launch { + val startTimestamp = System.currentTimeMillis() - timeFlow.value.inWholeMilliseconds + + while (isActive) { + delay(10) + val currentMs = System.currentTimeMillis() - startTimestamp + timeFlow.emit(currentMs.milliseconds) + } + } } private fun stopTimer() { - // TODO: Stop timer + timerJob?.cancel() } override fun onDestroyView() { @@ -91,12 +106,14 @@ class TimerFragment : Fragment() { private const val TIME = "time" private const val STARTED = "started" - private fun Duration.toDisplayString(): String = String.format( - Locale.getDefault(), - "%02d:%02d.%03d", - this.inWholeMinutes.toInt(), - this.inWholeSeconds.toInt(), - this.inWholeMilliseconds.toInt() - ) + private fun Duration.toDisplayString(): String { + return String.format( + Locale.getDefault(), "%02d:%02d.%03d", + inWholeMinutes % 60, + inWholeSeconds % 60, + inWholeMilliseconds % 1000 + + ) + } } } \ No newline at end of file