From 3ad3b235ea98b131b5acd242b1820f6523f829ab Mon Sep 17 00:00:00 2001 From: Anv0l Date: Thu, 22 May 2025 19:27:12 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=201.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/timer/TimerFragment.kt | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) 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..77a4602 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,16 +5,21 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding +import java.time.Instant import java.util.Locale +import kotlin.coroutines.coroutineContext import kotlin.properties.Delegates import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit +import kotlin.time.toDuration class TimerFragment : Fragment() { @@ -74,12 +79,32 @@ class TimerFragment : Fragment() { outState.putBoolean(STARTED, started) } + private var timer: Job? = null + private lateinit var timerStartedAt: Instant + + private fun getTimeDuration(): Duration { + return (Instant.now().toEpochMilli() - timerStartedAt.toEpochMilli()).toDuration( + DurationUnit.MILLISECONDS + ) + } + + private suspend fun doTimerLoops() { + while (coroutineContext.isActive) { + time = getTimeDuration() + delay(16) // 1/60 Hz + } + } + private fun startTimer() { - // TODO: Start timer + timerStartedAt = Instant.now() + timer = CoroutineScope(Dispatchers.Main).launch { + doTimerLoops() + } } private fun stopTimer() { - // TODO: Stop timer + timer?.cancel() + time = getTimeDuration() } override fun onDestroyView() { @@ -96,7 +121,7 @@ class TimerFragment : Fragment() { "%02d:%02d.%03d", this.inWholeMinutes.toInt(), this.inWholeSeconds.toInt(), - this.inWholeMilliseconds.toInt() + this.inWholeMilliseconds.toInt().mod(1000) ) } } \ No newline at end of file From 43046851ba5e01425baeb6df055825bbeb8bc465 Mon Sep 17 00:00:00 2001 From: Anv0l Date: Thu, 22 May 2025 22:18:26 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=201.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Не понятно, зачем нужно repeatOnLifecycle или я что-то не так сделал. --- .../ui/timer/TimerFragment.kt | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) 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 77a4602..4de4683 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,16 +5,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding import java.time.Instant import java.util.Locale -import kotlin.coroutines.coroutineContext import kotlin.properties.Delegates import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -26,9 +26,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) @@ -58,12 +56,17 @@ class TimerFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) savedInstanceState?.let { - time = it.getLong(TIME).milliseconds + timeFlow.value = it.getLong(TIME).milliseconds started = it.getBoolean(STARTED) } setButtonsState(started) with(binding) { - time.text = this@TimerFragment.time.toDisplayString() + lifecycleScope.launch { + timeFlow.collect { + time.text = this@TimerFragment.timeFlow.value.toDisplayString() + } + } + btnStart.setOnClickListener { started = true } @@ -75,11 +78,10 @@ class TimerFragment : Fragment() { 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 timer: Job? = null private lateinit var timerStartedAt: Instant private fun getTimeDuration(): Duration { @@ -88,23 +90,23 @@ class TimerFragment : Fragment() { ) } - private suspend fun doTimerLoops() { - while (coroutineContext.isActive) { - time = getTimeDuration() - delay(16) // 1/60 Hz + private fun doTimerLoops() = + flow { + while (started) { + emit(getTimeDuration()) + delay(16) // 1/60 Hz + } } - } private fun startTimer() { - timerStartedAt = Instant.now() - timer = CoroutineScope(Dispatchers.Main).launch { - doTimerLoops() + CoroutineScope(Dispatchers.Main).launch { + doTimerLoops().collect { duration -> timeFlow.value = duration } } + timerStartedAt = Instant.now() } private fun stopTimer() { - timer?.cancel() - time = getTimeDuration() + timeFlow.value = getTimeDuration() } override fun onDestroyView() { From 74245105c5c2dcff5a2d619736d5c64ce07ea654 Mon Sep 17 00:00:00 2001 From: Anv0l Date: Thu, 22 May 2025 22:51:22 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=202.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/LoginViewModel.kt | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) 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..1b546b9 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 @@ -3,6 +3,10 @@ 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.launch +import ru.otus.coroutineshomework.ui.login.data.Credentials class LoginViewModel : ViewModel() { @@ -15,13 +19,33 @@ class LoginViewModel : ViewModel() { * @param password user password */ fun login(name: String, password: String) { - // TODO: Implement login + _state.value = LoginViewState.LoggingIn + viewModelScope.launch(Dispatchers.IO) { + runCatching { + LoginApi().login( + Credentials( + name, + password + ) + ) + }.onFailure { + launch(Dispatchers.Main) { + _state.value = LoginViewState.Login(error = it as Exception) + } + }.onSuccess { + launch(Dispatchers.Main) { _state.value = LoginViewState.Content(it) } + } + } } /** * Logout from the network */ fun logout() { - // TODO: Implement logout + _state.value = LoginViewState.LoggingOut + viewModelScope.launch(Dispatchers.IO) { + LoginApi().logout() + } + _state.value = LoginViewState.Login() } } From 1e74cc5df18723bd7e7359d5bb706507c33cb5a7 Mon Sep 17 00:00:00 2001 From: Anv0l Date: Thu, 22 May 2025 23:16:28 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=202.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/LoginFragment.kt | 16 +++++--- .../ui/login/LoginViewModel.kt | 38 ++++++++++--------- 2 files changed, 30 insertions(+), 24 deletions(-) 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..4713edd 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,8 @@ import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.ContentBinding import ru.otus.coroutineshomework.databinding.FragmentLoginBinding import ru.otus.coroutineshomework.databinding.LoadingBinding @@ -43,12 +45,14 @@ 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() + lifecycleScope.launch { + 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 1b546b9..90ba4c0 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,17 +1,30 @@ 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.flow 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 fun loginFlow(credentials: Credentials): Flow = + flow { + val result = withContext(Dispatchers.IO) { + runCatching { LoginApi().login(credentials) } + } + result.onSuccess { emit(LoginViewState.Content(it)) } + result.onFailure { emit(LoginViewState.Login(it as Exception)) } + } /** * Login to the network @@ -20,20 +33,9 @@ class LoginViewModel : ViewModel() { */ fun login(name: String, password: String) { _state.value = LoginViewState.LoggingIn - viewModelScope.launch(Dispatchers.IO) { - runCatching { - LoginApi().login( - Credentials( - name, - password - ) - ) - }.onFailure { - launch(Dispatchers.Main) { - _state.value = LoginViewState.Login(error = it as Exception) - } - }.onSuccess { - launch(Dispatchers.Main) { _state.value = LoginViewState.Content(it) } + viewModelScope.launch { + loginFlow(Credentials(name, password)).collect { + _state.value = it } } } From 5ae5b5881649f0f92872cad6cf9f128c2900e082 Mon Sep 17 00:00:00 2001 From: Anv0l Date: Fri, 23 May 2025 00:40:44 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/network/NetworkViewModel.kt | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) 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..a6de0d8 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,13 @@ import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Deferred 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 +23,23 @@ class NetworkViewModel : ViewModel() { val result: LiveData = _result fun startTest(numberOfThreads: Int) { - // TODO: Implement the logic + _running.value = true + _result.value = null + + val deferredResults = mutableListOf>>() + + viewModelScope.launch { + repeat(numberOfThreads) { + deferredResults.add(async(Dispatchers.IO) { emulateBlockingNetworkRequest() }) + } + val results = deferredResults.awaitAll() + + val successCount = results.count { it.isSuccess } + + _running.value = false + _result.value = if (successCount == 0) null else + results.filter { res -> res.isSuccess }.sumOf { it.getOrNull() ?: 0 } / successCount + } } private companion object {