Login done (75%)

This commit is contained in:
A1pha 2024-11-21 20:53:33 +03:00
parent 1194f4d2ea
commit 4fe6238503
22 changed files with 308 additions and 110 deletions

View File

@ -5,16 +5,19 @@ import androidx.annotation.NonNull;
import java.util.function.Consumer;
import ru.myitschool.work.data.network.RetrofitFactory;
import ru.myitschool.work.data.source.Credentials;
import ru.myitschool.work.data.source.UserApi;
import ru.myitschool.work.domain.entities.Status;
import ru.myitschool.work.domain.entities.UserEntity;
import ru.myitschool.work.domain.login.LoginRepository;
import ru.myitschool.work.domain.user.UserRepository;
import ru.myitschool.work.utils.CallToConsumer;
public class UserRepositoryImplementation implements UserRepository {
public class UserRepositoryImplementation implements UserRepository, LoginRepository {
private static UserRepositoryImplementation INSTANCE;
private final UserApi userApi = RetrofitFactory.getInstance().getUserApi();
private final Credentials credentials = Credentials.getInstance();
private UserRepositoryImplementation() {}
@ -25,16 +28,16 @@ public class UserRepositoryImplementation implements UserRepository {
return INSTANCE;
}
@Override
public void getUserById(@NonNull String id, @NonNull Consumer<Status<UserEntity>> callback) {
public void getUserByLogin(@NonNull String login, @NonNull Consumer<Status<UserEntity>> callback) {
userApi.getById(id).enqueue(new CallToConsumer<>(
userApi.getByLogin(login).enqueue(new CallToConsumer<>(
callback,
userDto -> {
final String resultId = userDto.id;
final String resultLogin = userDto.login;
final String name = userDto.name;
if (resultId != null && name != null) {
if (resultLogin != null && name != null) {
return new UserEntity(
resultId,
resultLogin,
name,
userDto.lastVisit,
userDto.photoUrl,
@ -46,4 +49,17 @@ public class UserRepositoryImplementation implements UserRepository {
}
));
}
@Override
public void isUserExist(@NonNull String login, Consumer<Status<Void>> callback) {
userApi.isExist(login).enqueue(new CallToConsumer<>(
callback,
dto -> null
));
}
@Override
public void logoutUser() {
credentials.setAuthData(null);
}
}

View File

@ -10,16 +10,16 @@ public class UserDto {
@Nullable
@SerializedName("name")
public String name;
@Nullable
@SerializedName("lastVisit")
@Nullable
public String lastVisit;
@Nullable
@SerializedName("photoUrl")
@Nullable
public String photoUrl;
@Nullable
@SerializedName("position")
@Nullable
public String position;
@SerializedName("id")
@Nullable
public String id;
@SerializedName("login")
public String login;
}

View File

@ -1,5 +1,7 @@
package ru.myitschool.work.data.network;
import static ru.myitschool.work.core.Constants.SERVER_ADDRESS;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import ru.myitschool.work.data.UserRepositoryImplementation;
@ -16,8 +18,8 @@ public class RetrofitFactory {
return INSTANCE;
}
private Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://10.0.2.2:8080/")
private final Retrofit retrofit = new Retrofit.Builder()
.baseUrl(SERVER_ADDRESS)
.addConverterFactory(GsonConverterFactory.create())
.build();

View File

@ -0,0 +1,30 @@
package ru.myitschool.work.data.source;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class Credentials {
private static Credentials INSTANCE;
private Credentials() {}
public static synchronized Credentials getInstance() {
if (INSTANCE == null) {
INSTANCE = new Credentials();
}
return INSTANCE;
}
@Nullable
private String authData = null;
public void setAuthData(@Nullable String authData) {
this.authData = authData;
}
@Nullable
public String getAuthData() {
return authData;
}
}

View File

@ -7,6 +7,8 @@ import ru.myitschool.work.data.dto.UserDto;
public interface UserApi {
@GET("user/{id}")
Call<UserDto> getById(@Path("id") String id);
@GET("user/{login}")
Call<UserDto> getByLogin(@Path("login") String login);
@GET("user/username/{username}")
Call<Void> isExist(@Path("username") String login);
}

View File

@ -9,7 +9,7 @@ public class UserEntity {
@NonNull
private final String name;
@NonNull
private final String id;
private final String login;
@Nullable
private final String last_visit;
@Nullable
@ -19,7 +19,7 @@ public class UserEntity {
@NonNull
public String getId() {
return id;
return login;
}
@Nullable
@ -43,12 +43,12 @@ public class UserEntity {
}
public UserEntity(
@NonNull String id,
@NonNull String login,
@NonNull String name,
@Nullable String last_visit,
@Nullable String photoUrl,
@Nullable String position) {
this.id = id;
this.login = login;
this.name = name;
this.last_visit = last_visit;
this.photoUrl = photoUrl;

View File

@ -1,4 +0,0 @@
package ru.myitschool.work.domain.login;
public class CreateUserUseCase {
}

View File

@ -1,4 +1,29 @@
package ru.myitschool.work.domain.login;
import androidx.annotation.NonNull;
import java.util.function.Consumer;
import ru.myitschool.work.domain.entities.Status;
public class IsUserExistUseCase {
private final LoginRepository repository;
public IsUserExistUseCase(LoginRepository repository) {
this.repository = repository;
}
public void execute(@NonNull String login, Consumer<Status<Boolean>> callback) {
repository.isUserExist(login, status -> {
boolean isAvailable = status.getStatusCode() == 200 || status.getStatusCode() == 404;
callback.accept(
new Status<>(
status.getStatusCode(),
isAvailable ? status.getStatusCode() == 200 : null,
status.getErrors()
)
);
});
}
}

View File

@ -1,4 +1,13 @@
package ru.myitschool.work.domain.login;
import androidx.annotation.NonNull;
import java.util.function.Consumer;
import ru.myitschool.work.domain.entities.Status;
public interface LoginRepository {
void isUserExist(@NonNull String login, Consumer<Status<Void>> callback);
void logoutUser();
}

View File

@ -1,4 +0,0 @@
package ru.myitschool.work.domain.login;
public class LoginUserUseCase {
}

View File

@ -7,14 +7,14 @@ import java.util.function.Consumer;
import ru.myitschool.work.domain.entities.Status;
import ru.myitschool.work.domain.entities.UserEntity;
public class GetUserByIdUseCase {
public class GetUserByLoginUseCase {
private final UserRepository repository;
public GetUserByIdUseCase(UserRepository repository) {
public GetUserByLoginUseCase(UserRepository repository) {
this.repository = repository;
}
public void execute(@NonNull String id, @NonNull Consumer<Status<UserEntity>> callback) {
repository.getUserById(id, callback);
public void execute(@NonNull String login, @NonNull Consumer<Status<UserEntity>> callback) {
repository.getUserByLogin(login, callback);
}
}

View File

@ -10,5 +10,5 @@ import ru.myitschool.work.domain.entities.UserEntity;
public interface UserRepository {
void getUserById(@NonNull String id, @NonNull Consumer<Status<UserEntity>> callback);
void getUserByLogin(@NonNull String login, @NonNull Consumer<Status<UserEntity>> callback);
}

View File

@ -1,19 +1,9 @@
package ru.myitschool.work.ui
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.createGraph
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.fragment
import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R
import ru.myitschool.work.domain.user.GetUserByIdUseCase
import ru.myitschool.work.ui.login.LoginDestination
import ru.myitschool.work.ui.login.LoginFragment
import ru.myitschool.work.ui.qr.scan.QrScanDestination
import ru.myitschool.work.ui.qr.scan.QrScanFragment
// НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА!
@AndroidEntryPoint

View File

@ -1,6 +0,0 @@
package ru.myitschool.work.ui.login
import kotlinx.serialization.Serializable
@Serializable
data object LoginDestination

View File

@ -0,0 +1,63 @@
package ru.myitschool.work.ui.login;
import android.os.Bundle;
import android.text.Editable;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import ru.myitschool.work.R;
import ru.myitschool.work.databinding.FragmentLoginBinding;
import ru.myitschool.work.utils.OnChangeText;
public class LoginFragment extends Fragment {
private FragmentLoginBinding binding;
private LoginViewModel viewModel;
public LoginFragment() {
super(R.layout.fragment_login);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
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());
}
});
binding.login.setOnClickListener(v -> viewModel.confirm());
subscribe(viewModel);
}
private void subscribe(LoginViewModel viewModel) {
viewModel.errorLiveData.observe(getViewLifecycleOwner(), error -> {
Toast.makeText(getContext(), error, Toast.LENGTH_SHORT).show();
});
viewModel.stateLiveData.observe(getViewLifecycleOwner(), state -> {
binding.login.setClickable(state.isButtonActive());
});
viewModel.openProfileLiveData.observe(getViewLifecycleOwner(), (unused) -> {
View view = getView();
if (view == null) return;
Navigation.findNavController(getView()).navigate(
R.id.action_loginFragment_to_userFragment);
});
}
@Override
public void onDestroyView() {
binding = null;
super.onDestroyView();
}
}

View File

@ -1,36 +0,0 @@
package ru.myitschool.work.ui.login
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R
import ru.myitschool.work.databinding.FragmentLoginBinding
import ru.myitschool.work.utils.collectWhenStarted
import ru.myitschool.work.utils.visibleOrGone
@AndroidEntryPoint
class LoginFragment : Fragment(R.layout.fragment_login) {
private var _binding: FragmentLoginBinding? = null
private val binding: FragmentLoginBinding get() = _binding!!
private val viewModel: LoginViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentLoginBinding.bind(view)
subscribe()
}
private fun subscribe() {
viewModel.state.collectWhenStarted(this) { state ->
binding.loading.visibleOrGone(state)
}
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
}

View File

@ -0,0 +1,76 @@
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 ru.myitschool.work.data.UserRepositoryImplementation;
import ru.myitschool.work.domain.login.IsUserExistUseCase;
public class LoginViewModel extends ViewModel {
private final State INIT_STATE = new State(false);
private final MutableLiveData<State> mutableStateLiveData = new MutableLiveData<State>(
INIT_STATE
);
public final LiveData<State> stateLiveData = mutableStateLiveData;
private final MutableLiveData<String> mutableErrorLiveData = new MutableLiveData<String>();
public final LiveData<String> errorLiveData = mutableErrorLiveData;
private final MutableLiveData<Void> mutableOpenProfileLiveData = new MutableLiveData<Void>();
public final LiveData<Void> openProfileLiveData = mutableOpenProfileLiveData;
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;
}
isUserExistUseCase.execute(currentLogin, status -> {
if (status.getValue() == null || status.getErrors() != null) {
mutableErrorLiveData.postValue("Something went wrong. Try again later");
return;
}
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;
}
}
}

View File

@ -1,17 +0,0 @@
package ru.myitschool.work.ui.login
import android.content.Context
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
@ApplicationContext private val context: Context,
) : ViewModel() {
private val _state = MutableStateFlow(true)
val state = _state.asStateFlow()
}

View File

@ -9,14 +9,14 @@ import androidx.lifecycle.ViewModel;
import ru.myitschool.work.data.UserRepositoryImplementation;
import ru.myitschool.work.domain.entities.Status;
import ru.myitschool.work.domain.entities.UserEntity;
import ru.myitschool.work.domain.user.GetUserByIdUseCase;
import ru.myitschool.work.domain.user.GetUserByLoginUseCase;
public class UserViewModel extends ViewModel {
private final MutableLiveData<State> mutableStateLiveData = new MutableLiveData<State>();
public final LiveData<State> stateLiveData = mutableStateLiveData;
public final GetUserByIdUseCase getUserByIdUseCase = new GetUserByIdUseCase(
public final GetUserByLoginUseCase getUserByLoginUseCase = new GetUserByLoginUseCase(
UserRepositoryImplementation.getInstance()
);
@ -28,7 +28,7 @@ public class UserViewModel extends ViewModel {
public void update(@NonNull String id) {
mutableStateLiveData.setValue(new State(null, null, true));
getUserByIdUseCase.execute(id, status -> {
getUserByLoginUseCase.execute(id, status -> {
mutableStateLiveData.postValue(fromStatus(status));
});
}

View File

@ -0,0 +1,21 @@
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) {
}
}

View File

@ -1,16 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/loading"
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:flow_verticalStyle="packed"
app:flow_verticalGap="24dp"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="vertical"
app:constraint_referenced_ids="title, username, login" />
<TextView
android:textAppearance="@style/TextAppearance.AppCompat.Display1"
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
android:text="@string/welcome_text" />
<EditText
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/login_hint" />
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login"
android:clickable="false" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,4 +2,7 @@
<string name="app_name">NTO Pass</string>
<string name="logout_text">Выйти из аккаунта</string>
<string name="scan_qr_text">Сканировать QR-код</string>
<string name="login_hint">Введите логин</string>
<string name="welcome_text">Добро пожаловать</string>
<string name="login">Войти</string>
</resources>