From 5a5e051003050790dfebc2c2415713b6114a2225 Mon Sep 17 00:00:00 2001 From: xenia Date: Fri, 9 Jan 2026 15:18:33 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=201.1=20-=20=D0=A2=D0=B0=D0=B9=D0=BC=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/timer/TimerFragment.kt | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 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..59ce4e5 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 @@ -6,7 +6,10 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -15,6 +18,7 @@ import java.util.Locale import kotlin.properties.Delegates import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds class TimerFragment : Fragment() { @@ -34,6 +38,8 @@ class TimerFragment : Fragment() { } } + private var timerJob: Job? = null + private fun setButtonsState(started: Boolean) { with(binding) { btnStart.isEnabled = !started @@ -75,11 +81,18 @@ class TimerFragment : Fragment() { } private fun startTimer() { - // TODO: Start timer + timerJob?.cancel() + timerJob = viewLifecycleOwner.lifecycleScope.launch { + while (isActive) { + delay(1000L) + time += 1.seconds + } + } } private fun stopTimer() { - // TODO: Stop timer + timerJob?.cancel() + timerJob = null } override fun onDestroyView() { @@ -94,9 +107,12 @@ class TimerFragment : Fragment() { private fun Duration.toDisplayString(): String = String.format( Locale.getDefault(), "%02d:%02d.%03d", - this.inWholeMinutes.toInt(), - this.inWholeSeconds.toInt(), - this.inWholeMilliseconds.toInt() + this.inWholeMinutes, + this.inWholeSeconds % 60, + this.inWholeMilliseconds % 1000 + //this.inWholeMinutes.toInt(), + //this.inWholeSeconds.toInt(), + //this.inWholeMilliseconds.toInt() ) } } \ No newline at end of file From 6f5c455d34dea349dc708744a12db9abcfa82abb Mon Sep 17 00:00:00 2001 From: xenia Date: Fri, 9 Jan 2026 15:49:03 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=201.2=20-=20=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4=20?= =?UTF-8?q?=D1=82=D0=B0=D0=B9=D0=BC=D0=B5=D1=80=D0=B0=20=D0=BD=D0=B0=20Kot?= =?UTF-8?q?lin=20Flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/timer/TimerFragment.kt | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 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 59ce4e5..9473f93 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 @@ -6,11 +6,9 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import ru.otus.coroutineshomework.databinding.FragmentTimerBinding @@ -25,9 +23,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 var timeFlow: MutableStateFlow = MutableStateFlow(Duration.ZERO) private var started by Delegates.observable(false) { _, _, newValue -> setButtonsState(newValue) @@ -58,13 +54,21 @@ class TimerFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + savedInstanceState?.let { - time = it.getLong(TIME).milliseconds + timeFlow = MutableStateFlow(it.getLong(TIME).milliseconds) started = it.getBoolean(STARTED) } + setButtonsState(started) + + viewLifecycleOwner.lifecycleScope.launch { + timeFlow.collect { duration -> + binding.time.text = duration.toDisplayString() + } + } + with(binding) { - time.text = this@TimerFragment.time.toDisplayString() btnStart.setOnClickListener { started = true } @@ -76,7 +80,7 @@ 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) } @@ -85,7 +89,7 @@ class TimerFragment : Fragment() { timerJob = viewLifecycleOwner.lifecycleScope.launch { while (isActive) { delay(1000L) - time += 1.seconds + timeFlow.emit(timeFlow.value + 1.seconds) } } } @@ -110,9 +114,6 @@ class TimerFragment : Fragment() { this.inWholeMinutes, this.inWholeSeconds % 60, this.inWholeMilliseconds % 1000 - //this.inWholeMinutes.toInt(), - //this.inWholeSeconds.toInt(), - //this.inWholeMilliseconds.toInt() ) } } \ No newline at end of file From 5a324af26692e8c14eef1723ef7fb8ea88faa928 Mon Sep 17 00:00:00 2001 From: xenia Date: Fri, 9 Jan 2026 20:18:34 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=202.1=20-=20Login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/LoginViewModel.kt | 30 +++++++++++++++++-- 1 file changed, 28 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..48463ce 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,12 @@ 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.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import ru.otus.coroutineshomework.ui.login.data.Credentials class LoginViewModel : ViewModel() { @@ -15,13 +21,33 @@ class LoginViewModel : ViewModel() { * @param password user password */ fun login(name: String, password: String) { - // TODO: Implement login + viewModelScope.launch { + + _state.value = LoginViewState.LoggingIn + + try { + val user = withContext(Dispatchers.IO) { + LoginApi().login(Credentials(name, password)) + } + delay(3000L) + _state.value = LoginViewState.Content(user = user) + } catch (e: Exception) { + _state.value = LoginViewState.Login(error = e) + } + } } /** * Logout from the network */ fun logout() { - // TODO: Implement logout + viewModelScope.launch(Dispatchers.IO) { + LoginApi().logout() + withContext(Dispatchers.Main) { + _state.value = LoginViewState.LoggingOut + delay(3000L) + _state.value = LoginViewState.Login() + } + } } } From d3df3226cc27239a4847a77b478420c83239bc20 Mon Sep 17 00:00:00 2001 From: xenia Date: Fri, 9 Jan 2026 21:32:15 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=202.2=20-=20Login=20=D1=81=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC=20Kotlin?= =?UTF-8?q?=20Flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/LoginFragment.kt | 16 +++--- .../ui/login/LoginViewModel.kt | 49 +++++++++++-------- 2 files changed, 38 insertions(+), 27 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..78cdf96 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() + viewLifecycleOwner.lifecycleScope.launch { + loginViewModel.state.collect { + when (it) { + is LoginViewState.Login -> showLogin(it) + LoginViewState.LoggingIn -> showLoggingIn() + is LoginViewState.Content -> showContent(it) + 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 48463ce..40ade52 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,19 +1,35 @@ 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.delay +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() + + fun loginFlow(name: String, password: String): Flow = flow { + emit(LoginViewState.LoggingIn) + try { + val user = withContext(Dispatchers.IO) { + LoginApi().login(Credentials(name, password)) + } + delay(3000L) + emit(LoginViewState.Content(user = user)) + } catch (e: Exception) { + emit(LoginViewState.Login(error = e)) + } + } /** * Login to the network @@ -22,17 +38,8 @@ class LoginViewModel : ViewModel() { */ fun login(name: String, password: String) { viewModelScope.launch { - - _state.value = LoginViewState.LoggingIn - - try { - val user = withContext(Dispatchers.IO) { - LoginApi().login(Credentials(name, password)) - } - delay(3000L) - _state.value = LoginViewState.Content(user = user) - } catch (e: Exception) { - _state.value = LoginViewState.Login(error = e) + loginFlow(name = name, password = password).collect { viewState -> + _state.value = viewState } } } @@ -41,13 +48,13 @@ class LoginViewModel : ViewModel() { * Logout from the network */ fun logout() { - viewModelScope.launch(Dispatchers.IO) { - LoginApi().logout() - withContext(Dispatchers.Main) { - _state.value = LoginViewState.LoggingOut - delay(3000L) - _state.value = LoginViewState.Login() + viewModelScope.launch { + _state.value = LoginViewState.LoggingOut + withContext(Dispatchers.IO) { + LoginApi().logout() } + delay(3000L) + _state.value = LoginViewState.Login() } } } From 3b390702bcdd77b6608f7a66dc55b8b672ff5511 Mon Sep 17 00:00:00 2001 From: xenia Date: Sat, 10 Jan 2026 12:33:04 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=203.=20Speed-test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/network/NetworkViewModel.kt | 28 ++++++++++++++++++- 1 file changed, 27 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..6676cb8 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,11 @@ 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.isActive +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.random.Random @@ -18,7 +21,30 @@ class NetworkViewModel : ViewModel() { val result: LiveData = _result fun startTest(numberOfThreads: Int) { - // TODO: Implement the logic + viewModelScope.launch { + _running.value = true + + val networkResultsList = mutableListOf>() + + repeat(numberOfThreads) { + val networkResultDef = async { + emulateBlockingNetworkRequest() + } + networkResultsList.add(networkResultDef.await()) + } + + var divisor = 0L + var time = 0L + networkResultsList.forEach { networkResult -> + if (networkResult.isSuccess) { + networkResult.map { milliseconds -> time += milliseconds } + divisor += 1 + } + } + val averageTime = if (divisor != 0L) time /divisor else 0L + _result.value = averageTime + _running.value = false + } } private companion object {