From a15f1fb00ee75949e9ca94144f9354a9018bd52e Mon Sep 17 00:00:00 2001 From: a1pha Date: Wed, 19 Feb 2025 12:01:50 +0300 Subject: [PATCH] feat: moved fully to Kotlin and ktor --- app/build.gradle.kts | 6 +- .../java/ru/myitschool/work/core/Constants.kt | 1 + .../myitschool/work/data/QrRepositoryImpl.kt | 2 +- .../work/data/UserRepositoryImpl.kt | 39 ++-- .../data/local/CredentialsLocalDataSource.kt | 60 ++++-- .../work/domain/entities/Status.java | 34 ---- .../work/domain/login/AuthorizeUseCase.kt | 8 + .../work/domain/login/IsUserExistUseCase.kt | 7 +- .../work/domain/login/LoginRepository.kt | 1 + .../work/domain/login/LoginUseCase.kt | 4 +- .../work/domain/login/LogoutUseCase.kt | 8 + .../work/domain/user/GetCurrentUserUseCase.kt | 9 + .../domain/user/GetUserByLoginUseCase.java | 19 -- .../myitschool/work/ui/login/LoginFragment.kt | 121 +++++------ .../work/ui/login/LoginViewModel.kt | 183 +++++++++++------ .../work/ui/login/SplashFragment.kt | 30 ++- .../work/ui/profile/UserFragment.kt | 138 ++++--------- .../work/ui/profile/UserViewModel.kt | 109 +++++----- .../work/ui/qr/result/QrResultFragment.kt | 123 ++++++----- .../work/ui/qr/result/QrResultViewModel.kt | 93 ++++----- .../work/ui/qr/scan/QrScanFragment.kt | 6 +- .../work/ui/qr/scan/QrScanViewModel.kt | 4 +- .../myitschool/work/utils/CallToConsumer.java | 50 ----- .../myitschool/work/utils/FlowExtensions.kt | 2 +- .../work/utils/FragmentExtesions.kt | 4 +- .../myitschool/work/utils/OnChangeText.java | 21 -- .../work/utils/TextChangedListener.kt | 12 -- .../java/ru/myitschool/work/utils/Utils.java | 34 ---- app/src/main/res/layout/fragment_user.xml | 191 +++++++++--------- app/src/main/res/navigation/nav_graph.xml | 23 ++- 30 files changed, 626 insertions(+), 716 deletions(-) delete mode 100644 app/src/main/java/ru/myitschool/work/domain/entities/Status.java create mode 100644 app/src/main/java/ru/myitschool/work/domain/login/AuthorizeUseCase.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/login/LogoutUseCase.kt create mode 100644 app/src/main/java/ru/myitschool/work/domain/user/GetCurrentUserUseCase.kt delete mode 100644 app/src/main/java/ru/myitschool/work/domain/user/GetUserByLoginUseCase.java delete mode 100644 app/src/main/java/ru/myitschool/work/utils/CallToConsumer.java delete mode 100644 app/src/main/java/ru/myitschool/work/utils/OnChangeText.java delete mode 100644 app/src/main/java/ru/myitschool/work/utils/TextChangedListener.kt delete mode 100644 app/src/main/java/ru/myitschool/work/utils/Utils.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3ce1264..1eb6645 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,8 +37,10 @@ android { dependencies { defaultLibrary() - val ktorClientCore = "3.0.3" + implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01") + + val ktorClientCore = "3.0.3" implementation("io.ktor:ktor-client-cio:${ktorClientCore}") implementation("io.ktor:ktor-client-content-negotiation:${ktorClientCore}") implementation("io.ktor:ktor-client-core:${ktorClientCore}") @@ -56,7 +58,7 @@ dependencies { implementation(Dependencies.Retrofit.gsonConverter) implementation("com.squareup.picasso:picasso:2.8") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") implementation("androidx.datastore:datastore-preferences:1.1.2") implementation("com.google.mlkit:barcode-scanning:17.3.0") diff --git a/app/src/main/java/ru/myitschool/work/core/Constants.kt b/app/src/main/java/ru/myitschool/work/core/Constants.kt index b8152be..077e974 100644 --- a/app/src/main/java/ru/myitschool/work/core/Constants.kt +++ b/app/src/main/java/ru/myitschool/work/core/Constants.kt @@ -2,4 +2,5 @@ package ru.myitschool.work.core // БЕРИТЕ И ИЗМЕНЯЙТЕ ХОСТ ТОЛЬКО ЗДЕСЬ И НЕ БЕРИТЕ ИЗ ДРУГИХ МЕСТ. ФАЙЛ ПЕРЕМЕЩАТЬ НЕЛЬЗЯ object Constants { const val SERVER_ADDRESS = "http://192.168.1.103:8080" + const val TOKEN_KEY = "token" } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/QrRepositoryImpl.kt b/app/src/main/java/ru/myitschool/work/data/QrRepositoryImpl.kt index ca8c332..69976ab 100644 --- a/app/src/main/java/ru/myitschool/work/data/QrRepositoryImpl.kt +++ b/app/src/main/java/ru/myitschool/work/data/QrRepositoryImpl.kt @@ -10,6 +10,6 @@ class QrRepositoryImpl( ) : QrRepository { override suspend fun pushQr(qrEntity: QrEntity): Result { - return networkDataSource.pushQr(qrEntity, credentialsLocalDataSource.authData!!) + return networkDataSource.pushQr(qrEntity, credentialsLocalDataSource.getToken()) } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/UserRepositoryImpl.kt b/app/src/main/java/ru/myitschool/work/data/UserRepositoryImpl.kt index d74ef43..8c86cd6 100644 --- a/app/src/main/java/ru/myitschool/work/data/UserRepositoryImpl.kt +++ b/app/src/main/java/ru/myitschool/work/data/UserRepositoryImpl.kt @@ -1,5 +1,7 @@ package ru.myitschool.work.data +import android.util.Log +import ru.myitschool.work.data.dto.UserDto import ru.myitschool.work.data.local.CredentialsLocalDataSource import ru.myitschool.work.data.local.UserLocalDataSource import ru.myitschool.work.domain.entities.UserEntity @@ -9,31 +11,30 @@ import ru.myitschool.work.domain.user.UserRepository class UserRepositoryImpl( private val credentialsLocalDataSource: CredentialsLocalDataSource, - private val userLocalDatSource: UserLocalDataSource, + private val userLocalDataSource: UserLocalDataSource, private val networkDataSource: UserNetworkDataSource ) : LoginRepository, UserRepository { override suspend fun login(login: String, password: String): Result { return runCatching { - networkDataSource.login(credentialsLocalDataSource.setAuthData(login, password)) + networkDataSource.login(credentialsLocalDataSource.updateToken(login, password)) .onSuccess { dto -> - userLocalDatSource.cacheData( - UserEntity( - id = dto.id ?: error("Null user id"), - name = dto.name ?: error("Null user name"), - lastVisit = dto.lastVisit ?: error("Null user lastVisit"), - photoUrl = dto.photoUrl ?: error("Null user photoUrl"), - position = dto.position ?: error("Null user position") - ) - ) + map(dto).onSuccess { userLocalDataSource.cacheData(it) } } } } + override suspend fun authorize(token: String): Result { + return networkDataSource.login(token).fold( + onSuccess = { Result.success(Unit) }, + onFailure = { error -> Result.failure(error) } + ) + } + override suspend fun logout() { credentialsLocalDataSource.clear() - userLocalDatSource.clear() + userLocalDataSource.clear() } override suspend fun isUserExist(login: String): Result { @@ -41,6 +42,18 @@ class UserRepositoryImpl( } override suspend fun getCurrentUser(): UserEntity { - return userLocalDatSource.getUser()!! + return userLocalDataSource.getUser()!! + } + + private fun map(userDto: UserDto): Result { + return runCatching { + UserEntity( + id = userDto.id ?: error("Null user id"), + name = userDto.name ?: error("Null user name"), + lastVisit = userDto.lastVisit ?: error("Null user lastVisit"), + photoUrl = userDto.photoUrl ?: error("Null user photoUrl"), + position = userDto.position ?: error("Null user position") + ) + } } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/local/CredentialsLocalDataSource.kt b/app/src/main/java/ru/myitschool/work/data/local/CredentialsLocalDataSource.kt index 1c9ee7d..7f1a69f 100644 --- a/app/src/main/java/ru/myitschool/work/data/local/CredentialsLocalDataSource.kt +++ b/app/src/main/java/ru/myitschool/work/data/local/CredentialsLocalDataSource.kt @@ -1,35 +1,51 @@ -package ru.myitschool.work.data.local; +package ru.myitschool.work.data.local -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import android.content.SharedPreferences +import okhttp3.Credentials +import ru.myitschool.work.core.Constants.TOKEN_KEY -public class CredentialsLocalDataSource { +class CredentialsLocalDataSource private constructor(private val preferences: SharedPreferences) { + companion object { - private static CredentialsLocalDataSource INSTANCE; + @Volatile + private var INSTANCE: CredentialsLocalDataSource? = null - private CredentialsLocalDataSource() {} - - public static synchronized CredentialsLocalDataSource getInstance() { - if (INSTANCE == null) { - INSTANCE = new CredentialsLocalDataSource(); + fun getInstance(): CredentialsLocalDataSource { + return INSTANCE!! + } + + fun buildSource(sharedPreferences: SharedPreferences) { + INSTANCE = CredentialsLocalDataSource(sharedPreferences) } - return INSTANCE; } - @Nullable - private String authData = null; + private var savedToken: String? = preferences.getString(TOKEN_KEY, null) - public String setAuthData(@NonNull String login, @NonNull String password) { - this.authData = okhttp3.Credentials.basic(login, password); - return this.authData; + fun updateToken(login: String, password: String): String { + val updatedToken = Credentials.basic(login, password) + savedToken = updatedToken + cacheData() + return updatedToken } - public void clear() { - this.authData = null; + private fun cacheData() { + with(preferences.edit()) { + putString(TOKEN_KEY, savedToken) + apply() + } } - @Nullable - public String getAuthData() { - return authData; + fun getToken(): String { + return savedToken!! } -} + + fun getTokenForAuth(): Result { + return if (savedToken != null) Result.success(savedToken!!) else + Result.failure(IllegalStateException("User was not authorized")) + } + + fun clear() { + preferences.edit().clear().apply() + savedToken = null + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/entities/Status.java b/app/src/main/java/ru/myitschool/work/domain/entities/Status.java deleted file mode 100644 index e9c82ab..0000000 --- a/app/src/main/java/ru/myitschool/work/domain/entities/Status.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.myitschool.work.domain.entities; - -import javax.annotation.Nullable; - -public class Status { - - private final int statusCode; - - @Nullable - private final T value; - - @Nullable - private final Throwable errors; - - public Status(int statusCode, @Nullable T value, @Nullable Throwable errors) { - this.errors = errors; - this.statusCode = statusCode; - this.value = value; - } - - @Nullable - public Throwable getErrors() { - return errors; - } - - public int getStatusCode() { - return statusCode; - } - - @Nullable - public T getValue() { - return value; - } -} diff --git a/app/src/main/java/ru/myitschool/work/domain/login/AuthorizeUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/login/AuthorizeUseCase.kt new file mode 100644 index 0000000..18a374d --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/login/AuthorizeUseCase.kt @@ -0,0 +1,8 @@ +package ru.myitschool.work.domain.login + +class AuthorizeUseCase( + private val repository: LoginRepository +) { + + suspend operator fun invoke(token: String) : Result = repository.authorize(token) +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/login/IsUserExistUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/login/IsUserExistUseCase.kt index 2a1fecf..39a4d38 100644 --- a/app/src/main/java/ru/myitschool/work/domain/login/IsUserExistUseCase.kt +++ b/app/src/main/java/ru/myitschool/work/domain/login/IsUserExistUseCase.kt @@ -1,11 +1,10 @@ package ru.myitschool.work.domain.login -import android.util.Log -import kotlin.math.log - class IsUserExistUseCase( private val repository: LoginRepository ) { - suspend operator fun invoke(login: String) = repository.isUserExist(login) + suspend operator fun invoke(login: String): Result { + return repository.isUserExist(login) + } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/login/LoginRepository.kt b/app/src/main/java/ru/myitschool/work/domain/login/LoginRepository.kt index c8c5e73..054acc6 100644 --- a/app/src/main/java/ru/myitschool/work/domain/login/LoginRepository.kt +++ b/app/src/main/java/ru/myitschool/work/domain/login/LoginRepository.kt @@ -5,4 +5,5 @@ interface LoginRepository { suspend fun login(login: String, password: String) : Result suspend fun logout() suspend fun isUserExist(login: String): Result + suspend fun authorize(token: String): Result } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/login/LoginUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/login/LoginUseCase.kt index dc39396..078bd1c 100644 --- a/app/src/main/java/ru/myitschool/work/domain/login/LoginUseCase.kt +++ b/app/src/main/java/ru/myitschool/work/domain/login/LoginUseCase.kt @@ -4,6 +4,6 @@ class LoginUseCase( private val repository: LoginRepository ) { - operator fun invoke(login: String, password: String) = - repository.loginUser(login, password) + suspend operator fun invoke(login: String, password: String) = + repository.login(login, password) } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/login/LogoutUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/login/LogoutUseCase.kt new file mode 100644 index 0000000..e985e88 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/login/LogoutUseCase.kt @@ -0,0 +1,8 @@ +package ru.myitschool.work.domain.login + +class LogoutUseCase( + private val repository: LoginRepository +) { + + suspend operator fun invoke() = repository.logout() +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/user/GetCurrentUserUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/user/GetCurrentUserUseCase.kt new file mode 100644 index 0000000..15c4075 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/user/GetCurrentUserUseCase.kt @@ -0,0 +1,9 @@ +package ru.myitschool.work.domain.user + + +class GetCurrentUserUseCase( + private val repository: UserRepository +) { + + suspend operator fun invoke() = repository.getCurrentUser() +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/user/GetUserByLoginUseCase.java b/app/src/main/java/ru/myitschool/work/domain/user/GetUserByLoginUseCase.java deleted file mode 100644 index a9f79a8..0000000 --- a/app/src/main/java/ru/myitschool/work/domain/user/GetUserByLoginUseCase.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.myitschool.work.domain.user; - -import androidx.annotation.NonNull; - -import java.util.function.Consumer; - -import ru.myitschool.work.domain.entities.Status; - -public class GetUserByLoginUseCase { - private final UserRepository repository; - - public GetUserByLoginUseCase(UserRepository repository) { - this.repository = repository; - } - - public void execute(@NonNull String login, @NonNull Consumer> callback) { - repository.getUserByLogin(login, callback); - } -} diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt index 40aa2df..58d6cd7 100644 --- a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.kt @@ -1,81 +1,64 @@ -package ru.myitschool.work.ui.login; +package ru.myitschool.work.ui.login -import android.os.Bundle; -import android.text.Editable; -import android.view.View; +import android.os.Bundle +import android.util.Log +import android.view.View +import androidx.core.widget.doAfterTextChanged +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import ru.myitschool.work.R +import ru.myitschool.work.databinding.FragmentLoginBinding +import ru.myitschool.work.utils.collectWithLifecycle +import ru.myitschool.work.utils.visibleOrGone -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.res.ResourcesCompat; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.Navigation; +class LoginFragment : Fragment(R.layout.fragment_login) { -import ru.myitschool.work.R; -import ru.myitschool.work.databinding.FragmentLoginBinding; -import ru.myitschool.work.utils.OnChangeText; -import ru.myitschool.work.utils.Utils; + private var _binding: FragmentLoginBinding? = null + private val binding: FragmentLoginBinding get() = _binding!! -public class LoginFragment extends Fragment { - private FragmentLoginBinding binding; - private LoginViewModel viewModel; + private val viewModel by viewModels { LoginViewModel.Factory } - public LoginFragment() { - super(R.layout.fragment_login); - } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + _binding = FragmentLoginBinding.bind(view) - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - if (getContext() != null) { - if (Utils.getLogin(getContext()) != null && getView() != null) - Navigation.findNavController(getView()).navigate( - R.id.action_loginFragment_to_userFragment); + binding.username.doAfterTextChanged { login -> + viewModel.changeLogin(login = login.toString()) } - binding = FragmentLoginBinding.bind(view); - viewModel = new ViewModelProvider(this).get(LoginViewModel.class); - binding.username.addTextChangedListener(new OnChangeText() { - @Override - public void afterTextChanged(Editable s) { - super.afterTextChanged(s); - viewModel.changeLogin(s.toString()); + viewModel.state.collectWithLifecycle(this) { state -> + binding.username.isEnabled = state !is LoginViewModel.State.Loading + binding.password.isEnabled = state !is LoginViewModel.State.Loading + binding.error.visibleOrGone(state is LoginViewModel.State.Error) + + when (state) { + is LoginViewModel.State.Error -> binding.error.text = state.errorMessage + is LoginViewModel.State.Loading -> Unit + is LoginViewModel.State.Waiting -> Unit + is LoginViewModel.State.LoginCheckCompleted -> binding.login.isEnabled = + state.isCompleted } - }); - binding.login.setOnClickListener(v -> viewModel.confirm()); - subscribe(viewModel); + } + + viewModel.action.collectWithLifecycle(this) { action -> + when (action) { + is LoginViewModel.Action.OpenApp -> + findNavController().navigate(R.id.action_loginFragment_to_userFragment) + + is LoginViewModel.Action.GoToLogin -> Unit + } + } + + binding.login.setOnClickListener { + viewModel.onProcessClick( + login = binding.username.text.toString(), + password = binding.password.text.toString(), + ) + } } - - private void subscribe(LoginViewModel viewModel) { - viewModel.errorLiveData.observe(getViewLifecycleOwner(), error -> - binding.error.setVisibility(Utils.visibleOrGone(error != null))); - viewModel.stateLiveData.observe(getViewLifecycleOwner(), state -> { - binding.login.setEnabled(state.isButtonActive()); - if (state.isButtonActive()) { - binding.login.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.main_button, getContext().getTheme())); - binding.login.setTextColor(ResourcesCompat.getColor(getResources(), R.color.white, getContext().getTheme())); - } else { - binding.login.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.inactive_button, getContext().getTheme())); - binding.login.setTextColor(ResourcesCompat.getColor(getResources(), R.color.black, getContext().getTheme())); - } - }); - viewModel.openProfileLiveData.observe(getViewLifecycleOwner(), (unused) -> { - - if (getContext() != null) { - Utils.saveLogin(binding.username.getText().toString(), getContext()); - } - - if (getView() == null) return; - Navigation.findNavController(getView()).navigate( - R.id.action_loginFragment_to_userFragment); - }); + override fun onDestroy() { + _binding = null + super.onDestroy() } - - @Override - public void onDestroyView() { - binding = null; - super.onDestroyView(); - } -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt index 6d8064e..345b3d6 100644 --- a/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.kt @@ -1,73 +1,126 @@ -package ru.myitschool.work.ui.login; +package ru.myitschool.work.ui.login -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; +import android.app.Application +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import ru.myitschool.work.data.UserRepositoryImpl +import ru.myitschool.work.data.local.CredentialsLocalDataSource +import ru.myitschool.work.data.local.UserLocalDataSource +import ru.myitschool.work.data.network.UserNetworkDataSource +import ru.myitschool.work.domain.login.AuthorizeUseCase +import ru.myitschool.work.domain.login.IsUserExistUseCase +import ru.myitschool.work.domain.login.LoginUseCase -public class LoginViewModel extends ViewModel { +class LoginViewModel( + private val loginUseCase: LoginUseCase, + private val isUserExistUseCase: IsUserExistUseCase, + private val authorizeUseCase: AuthorizeUseCase, + private val application: Application +) : AndroidViewModel(application) { - private final State INIT_STATE = new State(false); - private final MutableLiveData mutableStateLiveData = new MutableLiveData(INIT_STATE); - public final LiveData stateLiveData = mutableStateLiveData; - private final MutableLiveData mutableErrorLiveData = new MutableLiveData(); - public final LiveData errorLiveData = mutableErrorLiveData; - private final MutableLiveData mutableOpenProfileLiveData = new MutableLiveData(); - public final LiveData openProfileLiveData = mutableOpenProfileLiveData; + private val _state = MutableStateFlow(State.Waiting) + private val _action = Channel( + capacity = Channel.BUFFERED, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val state = _state.asStateFlow() + val action = _action.receiveAsFlow() + private var userCheckCompleted = false + private var currentLogin: String? = null - private final IsUserExistUseCase isUserExistUseCase = new IsUserExistUseCase( - UserRepositoryImplementation.getInstance() - ); - - @Nullable - private String login = null; - private boolean userCheckCompleted = false; - public void changeLogin(@NonNull String login) { - this.login = login; - userCheckCompleted = !login.isBlank() && - login.length() >= 3 && - !Character.isDigit(login.charAt(0)) && - login.matches("^[a-zA-Z0-9]+$"); - - mutableStateLiveData.postValue(new State(userCheckCompleted)); - } - - public void confirm() { - checkUserExist(); - } - - private void checkUserExist() { - final String currentLogin = login; - if (currentLogin == null || currentLogin.isEmpty()) { - mutableErrorLiveData.postValue("Login cannot be null"); - return; + init { + viewModelScope.launch { + CredentialsLocalDataSource.getInstance().getTokenForAuth().fold( + onSuccess = { token -> + authorizeUseCase(token).fold( + onSuccess = { _action.send(Action.OpenApp) }, + onFailure = { _action.send(Action.GoToLogin) }) + }, + onFailure = { _action.send(Action.GoToLogin) } + ) } - isUserExistUseCase.execute(currentLogin, status -> { - if (status.getValue() == null || status.getErrors() != null) { - mutableErrorLiveData.postValue("Something went wrong. Try again later"); - return; + } + + fun changeLogin(login: String) { + viewModelScope.launch { + currentLogin = login + userCheckCompleted = login.isNotBlank() && login.length >= 3 && + !Character.isDigit(login[0]) && + login.matches("^[a-zA-Z0-9]+$".toRegex()) + + _state.emit(State.LoginCheckCompleted(userCheckCompleted)) + } + } + + fun onProcessClick(login: String, password: String) { + viewModelScope.launch { + _state.emit(State.Loading) + isUserExistUseCase.invoke(login).fold( + onSuccess = { response -> + if (response) { + loginUseCase.invoke(login, password).fold( + onSuccess = { openApp() }, + onFailure = { error -> + _state.emit(State.Error(error.message.toString())) + } + ) + } else { + _state.emit(State.Error("No such user or incorrect")) + } + }, + onFailure = { error -> _state.emit(State.Error(error.message.toString())) } + ) + } + } + + private fun openApp() { + viewModelScope.launch { + _action.send(Action.OpenApp) + } + } + + sealed interface State { + data object Waiting : State + data object Loading : State + data class Error(val errorMessage: String) : State + data class LoginCheckCompleted(val isCompleted: Boolean) : State + } + + sealed interface Action { + data object OpenApp : Action + data object GoToLogin : Action + } + + companion object { + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class, extras: CreationExtras): T { + val application = + extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]!! + + val repository = UserRepositoryImpl( + userLocalDataSource = UserLocalDataSource, + credentialsLocalDataSource = CredentialsLocalDataSource.getInstance(), + networkDataSource = UserNetworkDataSource + ) + + return LoginViewModel( + loginUseCase = LoginUseCase(repository), + isUserExistUseCase = IsUserExistUseCase(repository), + authorizeUseCase = AuthorizeUseCase(repository), + application = application + ) as T } - if (status.getStatusCode() == 401) { - mutableErrorLiveData.postValue("There is no such login or incorrect"); - } else if (status.getStatusCode() == 200) { - mutableOpenProfileLiveData.postValue(null); - } - }); - - } - - public class State { - private final boolean isButtonActive; - - public boolean isButtonActive() { - return isButtonActive; - } - - public State(boolean isButtonActive) { - this.isButtonActive = isButtonActive; } } - - -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/login/SplashFragment.kt b/app/src/main/java/ru/myitschool/work/ui/login/SplashFragment.kt index 4d1a1d8..500d5da 100644 --- a/app/src/main/java/ru/myitschool/work/ui/login/SplashFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/login/SplashFragment.kt @@ -1,18 +1,46 @@ package ru.myitschool.work.ui.login +import android.content.Context import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController import ru.myitschool.work.R +import ru.myitschool.work.core.Constants +import ru.myitschool.work.data.local.CredentialsLocalDataSource import ru.myitschool.work.databinding.FragmentSplashBinding +import ru.myitschool.work.utils.collectWithLifecycle -class SplashFragment: Fragment(R.layout.fragment_splash) { +class SplashFragment : Fragment(R.layout.fragment_splash) { private var _binding: FragmentSplashBinding? = null private val binding: FragmentSplashBinding get() = _binding!! + private val viewModel by viewModels { LoginViewModel.Factory } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + CredentialsLocalDataSource.buildSource( + requireActivity().getSharedPreferences( + Constants.TOKEN_KEY, + Context.MODE_PRIVATE + ) + ) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { _binding = FragmentSplashBinding.bind(view) + + viewModel.action.collectWithLifecycle(this) { action -> + val navController = findNavController() + when (action) { + is LoginViewModel.Action.GoToLogin -> navController.navigate(R.id.loginFragment) + is LoginViewModel.Action.OpenApp -> + navController.navigate(R.id.action_loginFragment_to_userFragment) + } + + } } override fun onDestroy() { diff --git a/app/src/main/java/ru/myitschool/work/ui/profile/UserFragment.kt b/app/src/main/java/ru/myitschool/work/ui/profile/UserFragment.kt index e4e0008..0add493 100644 --- a/app/src/main/java/ru/myitschool/work/ui/profile/UserFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/profile/UserFragment.kt @@ -1,109 +1,55 @@ -package ru.myitschool.work.ui.profile; +package ru.myitschool.work.ui.profile -import static ru.myitschool.work.ui.qr.result.QrResultFragment.RESPONSE_KEY; +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.squareup.picasso.Picasso +import ru.myitschool.work.R +import ru.myitschool.work.databinding.FragmentUserBinding +import ru.myitschool.work.utils.collectWithLifecycle +import ru.myitschool.work.utils.visibleOrGone -import android.os.Bundle; -import android.view.View; +class UserFragment : Fragment(R.layout.fragment_user) { -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentResultListener; -import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.Navigation; + private var _binding: FragmentUserBinding? = null + private val binding: FragmentUserBinding get() = _binding!! -import com.squareup.picasso.Picasso; + private val viewModel by viewModels { UserViewModel.Factory } -import java.text.MessageFormat; + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + _binding = FragmentUserBinding.bind(view) -import ru.myitschool.work.R; -import ru.myitschool.work.databinding.FragmentUserBinding; -import ru.myitschool.work.ui.qr.scan.QrScanDestination; -import ru.myitschool.work.utils.Utils; - -public class UserFragment extends Fragment { - - private FragmentUserBinding binding; - private UserViewModel viewModel; - - public UserFragment() { - super(R.layout.fragment_user); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - binding = FragmentUserBinding.bind(view); - - viewModel = new ViewModelProvider(this).get(UserViewModel.class); - viewModel.stateLiveData.observe(getViewLifecycleOwner(), state -> { - UserEntity entity = state.getItem(); - if (state.getErrorMessage() != null) { - binding.error.setVisibility(View.VISIBLE); - binding.error.setText(state.getErrorMessage()); - - binding.logout.setVisibility(View.GONE); - binding.scan.setVisibility(View.GONE); - } else if (entity != null) { - binding.photo.setVisibility(Utils.visibleOrGone(entity.getPhotoUrl() != null)); - binding.position.setVisibility(Utils.visibleOrGone(entity.getPosition() != null)); - binding.lastEntry.setVisibility(Utils.visibleOrGone(entity.getLast_visit() != null)); - - binding.fullname.setText(entity.getName()); - binding.position.setText(entity.getPosition()); - String lastVisit = entity.getLast_visit(); - binding.lastEntry.setText(MessageFormat.format("{0} {1}", - lastVisit.substring(0, 10), lastVisit.substring(11, 16))); - - if (entity.getPhotoUrl() != null) { - Picasso.get().load(entity.getPhotoUrl()).into(binding.photo); + viewModel.state.collectWithLifecycle(this) { state -> + (binding.refresh as SwipeRefreshLayout).isRefreshing = + state is UserViewModel.State.Loading + binding.content?.visibleOrGone(state is UserViewModel.State.Show) + when (state) { + is UserViewModel.State.Loading -> Unit + is UserViewModel.State.Show -> { + val user = state.userEntity + binding.fullname.text = user.name + binding.position.text = user.position + binding.lastEntry.text = user.lastVisit + Picasso.get().load(user.photoUrl).into(binding.photo) } } - }); - if (getContext() != null && Utils.getLogin(getContext()) != null) { - viewModel.update(Utils.getLogin(getContext())); + (binding.refresh as SwipeRefreshLayout).setOnRefreshListener { + viewModel.onRefresh() + } + + binding.logout.setOnClickListener { + viewModel.onLogout() + findNavController().navigate(R.id.action_userFragment_to_loginFragment) + } } - - binding.refresh.setOnClickListener(v -> { - if (getContext() != null && Utils.getLogin(getContext()) != null) { - viewModel.update(Utils.getLogin(getContext())); - } - }); - - binding.scan.setOnClickListener(v -> { - if (getView() != null) - Navigation.findNavController(getView()).navigate( - R.id.action_userFragment_to_qrScanFragment); - }); - - binding.logout.setOnClickListener(v -> { - if (getContext() != null) { - Utils.deleteLogin(getContext()); - } - if (getView() != null) { - Navigation.findNavController(getView()).navigate( - R.id.action_userFragment_to_loginFragment); - } - }); - - getParentFragmentManager().setFragmentResultListener(QrScanDestination.REQUEST_KEY, this, new FragmentResultListener() { - @Override - public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) { - getParentFragmentManager().setFragmentResult(RESPONSE_KEY, result); - if (getView() != null) - Navigation.findNavController(getView()).navigate( - R.id.action_userFragment_to_qrResultFragment); - } - }); - } - - @Override - public void onDestroyView() { - binding = null; - super.onDestroyView(); + override fun onDestroy() { + _binding = null + super.onDestroy() } - -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.kt index 07307c3..d037094 100644 --- a/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.kt @@ -1,60 +1,65 @@ -package ru.myitschool.work.ui.profile; +package ru.myitschool.work.ui.profile -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import ru.myitschool.work.data.UserRepositoryImpl +import ru.myitschool.work.data.local.CredentialsLocalDataSource +import ru.myitschool.work.data.local.UserLocalDataSource +import ru.myitschool.work.data.network.UserNetworkDataSource +import ru.myitschool.work.domain.entities.UserEntity +import ru.myitschool.work.domain.login.LogoutUseCase +import ru.myitschool.work.domain.user.GetCurrentUserUseCase -import ru.myitschool.work.domain.user.GetUserByLoginUseCase; +class UserViewModel( + private val getCurrentUserUseCase: GetCurrentUserUseCase, + private val logoutUseCase: LogoutUseCase +) : ViewModel() { -public class UserViewModel extends ViewModel { + private val _state = MutableStateFlow(State.Loading) + val state = _state.asStateFlow() - private final MutableLiveData mutableStateLiveData = new MutableLiveData<>(); - public final LiveData stateLiveData = mutableStateLiveData; - - public final GetUserByLoginUseCase getUserByLoginUseCase = new GetUserByLoginUseCase( - UserRepositoryImplementation.getInstance() - ); - - public void update(@NonNull String login) { - mutableStateLiveData.setValue(new State(null, null, true)); - getUserByLoginUseCase.execute(login, status -> { - mutableStateLiveData.postValue(new State( - status.getErrors() != null ? status.getErrors().getLocalizedMessage() : null, - status.getValue(), - false - )); - }); + init { + updateState() } - public class State { - @Nullable - private final String errorMessage; - - @Nullable - private final UserEntity item; - - private final boolean isLoading; - - @Nullable - public String getErrorMessage() { - return errorMessage; - } - - @Nullable - public UserEntity getItem() { - return item; - } - - public boolean isLoading() { - return isLoading; - } - - public State(@Nullable String errorMessage, @Nullable UserEntity item, boolean isLoading) { - this.errorMessage = errorMessage; - this.item = item; - this.isLoading = isLoading; + private fun updateState() { + viewModelScope.launch { + State.Show(getCurrentUserUseCase()) } } -} + + fun onRefresh() { + updateState() + } + + fun onLogout() { + viewModelScope.launch { logoutUseCase() } + } + + sealed interface State { + data object Loading : State + data class Show(val userEntity: UserEntity) : State + } + + companion object { + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class, extras: CreationExtras): T { + val repository = UserRepositoryImpl( + credentialsLocalDataSource = CredentialsLocalDataSource.getInstance(), + userLocalDataSource = UserLocalDataSource, + networkDataSource = UserNetworkDataSource + ) + return UserViewModel( + getCurrentUserUseCase = GetCurrentUserUseCase(repository = repository), + logoutUseCase = LogoutUseCase(repository = repository) + ) as T + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.kt b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.kt index a09b34f..a56876d 100644 --- a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.kt @@ -1,79 +1,78 @@ -package ru.myitschool.work.ui.qr.result; +package ru.myitschool.work.ui.qr.result -import android.os.Bundle; -import android.util.Log; -import android.view.View; +import android.os.Bundle +import android.view.View +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import ru.myitschool.work.R +import ru.myitschool.work.databinding.FragmentQrResultBinding +import ru.myitschool.work.ui.qr.scan.QrScanDestination +import ru.myitschool.work.ui.qr.scan.QrScanDestination.getDataIfExist +import ru.myitschool.work.utils.collectWithLifecycle -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.res.ResourcesCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentResultListener; -import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.Navigation; -import ru.myitschool.work.R; -import ru.myitschool.work.databinding.FragmentQrResultBinding; -import ru.myitschool.work.ui.qr.scan.QrScanDestination; -import ru.myitschool.work.utils.Utils; +const val RESPONSE_KEY: String = "response_qr" -public class QrResultFragment extends Fragment { +class QrResultFragment : Fragment(R.layout.fragment_qr_result) { - public static final String RESPONSE_KEY = "response_qr"; - private FragmentQrResultBinding binding; - private String resultQr; - private QrResultViewModel viewModel; + private var _binding: FragmentQrResultBinding? = null + private val binding: FragmentQrResultBinding get() = _binding!! - public QrResultFragment() { - super(R.layout.fragment_qr_result); - } + private var _resultQr: String? = null + private val resultQr: String = _resultQr!! - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - if (savedInstanceState != null) - resultQr = savedInstanceState.getString(QrScanDestination.REQUEST_KEY); - binding = FragmentQrResultBinding.bind(view); - viewModel = new ViewModelProvider(this).get(QrResultViewModel.class); + private val viewModel by viewModels { QrResultViewModel.Factory } - getParentFragmentManager().setFragmentResultListener(RESPONSE_KEY, this, new FragmentResultListener() { - @Override - public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) { - resultQr = QrScanDestination.INSTANCE.getDataIfExist(result); - viewModel.update(Utils.getLogin(getContext()), resultQr); + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + _binding = FragmentQrResultBinding.bind(view) + + if (savedInstanceState != null) { + _resultQr = savedInstanceState.getString(QrScanDestination.REQUEST_KEY) + } + + parentFragmentManager.setFragmentResultListener(RESPONSE_KEY, this) { _, result -> + _resultQr = getDataIfExist(result) + viewModel.update(resultQr) + } + + viewModel.state.collectWithLifecycle(this) { state -> + if (_resultQr == null) { + binding.result.setText(R.string.door_closed) + binding.close.background = + ContextCompat.getDrawable(requireContext(), R.drawable.warn_button) } - }); - viewModel.stateLiveData.observe(getViewLifecycleOwner(), state -> { - if (resultQr == null) { - binding.result.setText(R.string.door_closed); - binding.close.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.warn_button, getContext().getTheme())); - } else if (state.isOpened()) { - binding.result.setText(R.string.door_opened); - binding.close.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.main_button, getContext().getTheme())); - } else { - binding.result.setText(R.string.error); - binding.close.setBackground(ResourcesCompat.getDrawable(getResources(), R.drawable.warn_button, getContext().getTheme())); + when (state) { + is QrResultViewModel.State.Error -> { + binding.result.text = state.message + binding.close.background = + ContextCompat.getDrawable(requireContext(), R.drawable.warn_button) + } + + is QrResultViewModel.State.Loading -> Unit + is QrResultViewModel.State.Show -> { + binding.result.setText(R.string.door_opened) + binding.close.background = + ContextCompat.getDrawable(requireContext(), R.drawable.main_button) + } } - }); + } - binding.close.setOnClickListener(v -> { - if (getView() != null) { - Navigation.findNavController(getView()).navigate( - R.id.action_qrResultFragment_to_userFragment); - } - }); + binding.close.setOnClickListener { + findNavController().navigate(R.id.action_qrResultFragment_to_userFragment) + } } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(QrScanDestination.REQUEST_KEY, resultQr); + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString(QrScanDestination.REQUEST_KEY, resultQr) } - @Override - public void onDestroy() { - binding = null; - super.onDestroy(); + override fun onDestroy() { + _binding = null + super.onDestroy() } -} +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt index 74b9a04..e69739c 100644 --- a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.kt @@ -1,52 +1,53 @@ -package ru.myitschool.work.ui.qr.result; +package ru.myitschool.work.ui.qr.result -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import ru.myitschool.work.data.QrNetworkDataSource +import ru.myitschool.work.data.QrRepositoryImpl +import ru.myitschool.work.data.local.CredentialsLocalDataSource +import ru.myitschool.work.domain.entities.QrEntity +import ru.myitschool.work.domain.qr.PushQrUseCase -import ru.myitschool.work.domain.entities.Status; -import ru.myitschool.work.domain.qr.PushQrUseCase; +class QrResultViewModel( + private val pushQrUseCase: PushQrUseCase +) : ViewModel() { -public class QrResultViewModel extends ViewModel { - private final MutableLiveData mutableStateLiveData = new MutableLiveData(); - public final LiveData stateLiveData = mutableStateLiveData; + private val _state = MutableStateFlow(State.Loading) + val state = _state.asStateFlow() - public final PushQrUseCase pushQrUseCase = new PushQrUseCase( - QrRepositoryImplementation.getInstance() - ); - - public void update(@NonNull String login, @NonNull String qr) { - pushQrUseCase.execute(new QrEntity(login, qr), status -> mutableStateLiveData.postValue(fromStatus(status))); - } - - private State fromStatus(Status status) { - return new State( - status.getErrors() != null ? status.getErrors().getLocalizedMessage(): null, - status.getValue() != null ? status.getValue() : false - ); - } - - - public class State { - - @Nullable - private final String errorMessage; - private final boolean isOpened; - - @Nullable - public String getErrorMessage() { - return errorMessage; - } - - public boolean isOpened() { - return isOpened; - } - - public State(@Nullable String errorMessage, boolean isOpened) { - this.errorMessage = errorMessage; - this.isOpened = isOpened; + fun update(qrValue: String) { + viewModelScope.launch { + pushQrUseCase(QrEntity(code = qrValue)).fold( + onSuccess = { _state.emit(State.Show) }, + onFailure = { _state.emit(State.Error(it.message.toString())) } + ) } } -} + + sealed interface State { + data object Loading : State + data object Show : State + data class Error(val message: String) : State + } + + companion object { + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class, extras: CreationExtras): T { + return QrResultViewModel( + pushQrUseCase = PushQrUseCase( + repository = QrRepositoryImpl( + networkDataSource = QrNetworkDataSource, + credentialsLocalDataSource = CredentialsLocalDataSource.getInstance() + ) + ) + ) as T + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt index a9ddaab..11eee64 100644 --- a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanFragment.kt @@ -20,7 +20,7 @@ import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.common.Barcode import ru.myitschool.work.R import ru.myitschool.work.databinding.FragmentQrScanBinding -import ru.myitschool.work.utils.collectWhenStarted +import ru.myitschool.work.utils.collectWithLifecycle import ru.myitschool.work.utils.visibleOrGone // НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ @@ -49,7 +49,7 @@ class QrScanFragment : Fragment(R.layout.fragment_qr_scan) { } private fun subscribe() { - viewModel.state.collectWhenStarted(this) { state -> + viewModel.state.collectWithLifecycle(this) { state -> binding.loading.visibleOrGone(state is QrScanViewModel.State.Loading) binding.viewFinder.visibleOrGone(state is QrScanViewModel.State.Scan) if (!isCameraInit && state is QrScanViewModel.State.Scan) { @@ -58,7 +58,7 @@ class QrScanFragment : Fragment(R.layout.fragment_qr_scan) { } } - viewModel.action.collectWhenStarted(this) { action -> + viewModel.action.collectWithLifecycle(this) { action -> when (action) { is QrScanViewModel.Action.RequestPermission -> requestPermission(action.permission) is QrScanViewModel.Action.CloseWithCancel -> { diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt index 14565ab..7329db2 100644 --- a/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/qr/scan/QrScanViewModel.kt @@ -13,14 +13,14 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import ru.myitschool.work.utils.MutablePublishFlow +import ru.myitschool.work.utils.mutablePublishFlow // НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ class QrScanViewModel( application: Application ) : AndroidViewModel(application) { - private val _action = MutablePublishFlow() + private val _action = mutablePublishFlow() val action = _action.asSharedFlow() private val _state = MutableStateFlow(initialState) diff --git a/app/src/main/java/ru/myitschool/work/utils/CallToConsumer.java b/app/src/main/java/ru/myitschool/work/utils/CallToConsumer.java deleted file mode 100644 index c8ff667..0000000 --- a/app/src/main/java/ru/myitschool/work/utils/CallToConsumer.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.myitschool.work.utils; - - -import androidx.annotation.NonNull; - -import java.util.function.Consumer; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import ru.myitschool.work.domain.entities.Status; - -public class CallToConsumer implements Callback { - @NonNull - private final Consumer> callback; - @NonNull - private final Mapper mapper; - - public CallToConsumer(@NonNull Consumer> callback, - @NonNull Mapper mapper) { - this.callback = callback; - this.mapper = mapper; - } - - @Override - public void onResponse(@NonNull Call call, Response response) { - callback.accept( - new Status<>( - response.code(), - mapper.map(response.body()), - null - ) - ); - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - callback.accept( - new Status<>( - -1, - null, - t - ) - ); - } - - public interface Mapper { - DEST map(SOURCE source); - } -} diff --git a/app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt b/app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt index 87bccc2..28a4998 100644 --- a/app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt +++ b/app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt @@ -3,7 +3,7 @@ package ru.myitschool.work.utils import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow -fun MutablePublishFlow() = MutableSharedFlow( +fun mutablePublishFlow() = MutableSharedFlow( replay = 0, extraBufferCapacity = 1, BufferOverflow.DROP_OLDEST diff --git a/app/src/main/java/ru/myitschool/work/utils/FragmentExtesions.kt b/app/src/main/java/ru/myitschool/work/utils/FragmentExtesions.kt index 8c99ef3..3faef06 100644 --- a/app/src/main/java/ru/myitschool/work/utils/FragmentExtesions.kt +++ b/app/src/main/java/ru/myitschool/work/utils/FragmentExtesions.kt @@ -6,13 +6,13 @@ import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch -inline fun Flow.collectWhenStarted( +inline fun Flow.collectWithLifecycle( fragment: Fragment, crossinline collector: (T) -> Unit ) { fragment.viewLifecycleOwner.lifecycleScope.launch { flowWithLifecycle(fragment.viewLifecycleOwner.lifecycle).collect { value -> - collector.invoke(value) + collector(value) } } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/utils/OnChangeText.java b/app/src/main/java/ru/myitschool/work/utils/OnChangeText.java deleted file mode 100644 index 504d821..0000000 --- a/app/src/main/java/ru/myitschool/work/utils/OnChangeText.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.myitschool.work.utils; - -import android.text.Editable; -import android.text.TextWatcher; - -public class OnChangeText implements TextWatcher { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - - } -} diff --git a/app/src/main/java/ru/myitschool/work/utils/TextChangedListener.kt b/app/src/main/java/ru/myitschool/work/utils/TextChangedListener.kt deleted file mode 100644 index c81147d..0000000 --- a/app/src/main/java/ru/myitschool/work/utils/TextChangedListener.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ru.myitschool.work.utils - -import android.text.Editable -import android.text.TextWatcher - -open class TextChangedListener: TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit - - override fun afterTextChanged(s: Editable?) = Unit -} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/utils/Utils.java b/app/src/main/java/ru/myitschool/work/utils/Utils.java deleted file mode 100644 index 5617742..0000000 --- a/app/src/main/java/ru/myitschool/work/utils/Utils.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.myitschool.work.utils; - -import static android.view.View.GONE; -import static android.view.View.VISIBLE; - -import android.content.Context; -import android.content.SharedPreferences; - - -public class Utils { - - public static int visibleOrGone(boolean isVisible) { - return isVisible ? VISIBLE : GONE; - } - public static void saveLogin(String login, Context context) { - SharedPreferences sharedPreferences = context.getSharedPreferences("login", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString("login", login); - editor.commit(); - } - - public static void deleteLogin(Context context) { - SharedPreferences sharedPreferences = context.getSharedPreferences("login", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString("login", null); - editor.commit(); - } - - public static String getLogin(Context context) { - SharedPreferences preferences = context.getSharedPreferences( - "login", Context.MODE_PRIVATE); - return preferences.getString("login", null); - } -} diff --git a/app/src/main/res/layout/fragment_user.xml b/app/src/main/res/layout/fragment_user.xml index bce3a98..ffd211c 100644 --- a/app/src/main/res/layout/fragment_user.xml +++ b/app/src/main/res/layout/fragment_user.xml @@ -1,112 +1,111 @@ - - - - - - - - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginHorizontal="20dp" + android:layout_marginTop="130dp"> + android:layout_height="match_parent" + android:orientation="vertical"> - + android:gravity="center_horizontal" + android:orientation="vertical"> - + + + + + + + + + + + + +