From 5101f94bd51ecab013c09006b87ba58b29ef91cb Mon Sep 17 00:00:00 2001 From: A1pha Date: Fri, 22 Nov 2024 17:32:44 +0300 Subject: [PATCH] Qr result done --- .../data/UserRepositoryImplementation.java | 18 ++++- .../ru/myitschool/work/data/dto/QrDto.java | 15 ++++ .../ru/myitschool/work/data/dto/UserDto.java | 5 +- .../myitschool/work/data/source/UserApi.java | 9 ++- .../work/domain/entities/QrEntity.java | 27 +++++++ .../work/domain/entities/UserEntity.java | 14 +++- .../work/domain/login/IsUserExistUseCase.java | 2 +- .../work/domain/qr/PushQrUseCase.java | 28 +++++++ .../work/domain/qr/QrRepository.java | 13 ++++ .../work/ui/login/LoginFragment.java | 24 +++++- .../work/ui/login/LoginViewModel.java | 5 ++ .../work/ui/profile/UserFragment.java | 60 +++++++++++---- .../work/ui/profile/UserViewModel.java | 8 +- .../work/ui/qr/result/QrResultFragment.java | 73 +++++++++++++++++++ .../work/ui/qr/result/QrResultViewModel.java | 58 +++++++++++++++ .../main/res/layout/fragment_qr_result.xml | 36 +++++++++ app/src/main/res/layout/fragment_user.xml | 8 +- app/src/main/res/navigation/nav_graph.xml | 14 ++++ app/src/main/res/values/strings.xml | 4 + 19 files changed, 392 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/ru/myitschool/work/data/dto/QrDto.java create mode 100644 app/src/main/java/ru/myitschool/work/domain/entities/QrEntity.java create mode 100644 app/src/main/java/ru/myitschool/work/domain/qr/PushQrUseCase.java create mode 100644 app/src/main/java/ru/myitschool/work/domain/qr/QrRepository.java create mode 100644 app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.java create mode 100644 app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.java create mode 100644 app/src/main/res/layout/fragment_qr_result.xml diff --git a/app/src/main/java/ru/myitschool/work/data/UserRepositoryImplementation.java b/app/src/main/java/ru/myitschool/work/data/UserRepositoryImplementation.java index 8bbd742..113dacc 100644 --- a/app/src/main/java/ru/myitschool/work/data/UserRepositoryImplementation.java +++ b/app/src/main/java/ru/myitschool/work/data/UserRepositoryImplementation.java @@ -4,16 +4,19 @@ import androidx.annotation.NonNull; import java.util.function.Consumer; +import ru.myitschool.work.data.dto.QrDto; 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.QrEntity; 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.qr.QrRepository; import ru.myitschool.work.domain.user.UserRepository; import ru.myitschool.work.utils.CallToConsumer; -public class UserRepositoryImplementation implements UserRepository, LoginRepository { +public class UserRepositoryImplementation implements UserRepository, LoginRepository, QrRepository { private static UserRepositoryImplementation INSTANCE; private final UserApi userApi = RetrofitFactory.getInstance().getUserApi(); @@ -34,9 +37,11 @@ public class UserRepositoryImplementation implements UserRepository, LoginReposi callback, userDto -> { final String resultLogin = userDto.login; + final String id = userDto.id; final String name = userDto.name; - if (resultLogin != null && name != null) { + if (resultLogin != null && id != null && name != null) { return new UserEntity( + id, resultLogin, name, userDto.lastVisit, @@ -62,4 +67,13 @@ public class UserRepositoryImplementation implements UserRepository, LoginReposi public void logoutUser() { credentials.setAuthData(null); } + + @Override + public void pushQr(@NonNull QrEntity qrEntity, @NonNull Consumer> callback) { + userApi.openDoor(qrEntity.getLogin(), + new QrDto(qrEntity.getQr())).enqueue(new CallToConsumer<>( + callback, + dto -> null + )); + } } diff --git a/app/src/main/java/ru/myitschool/work/data/dto/QrDto.java b/app/src/main/java/ru/myitschool/work/data/dto/QrDto.java new file mode 100644 index 0000000..0b6543c --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/QrDto.java @@ -0,0 +1,15 @@ +package ru.myitschool.work.data.dto; + +import androidx.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +public class QrDto { + @Nullable + @SerializedName("code") + public String code; + + public QrDto(@Nullable String code) { + this.code = code; + } +} diff --git a/app/src/main/java/ru/myitschool/work/data/dto/UserDto.java b/app/src/main/java/ru/myitschool/work/data/dto/UserDto.java index 5da13f5..7bda6e6 100644 --- a/app/src/main/java/ru/myitschool/work/data/dto/UserDto.java +++ b/app/src/main/java/ru/myitschool/work/data/dto/UserDto.java @@ -7,6 +7,9 @@ import com.google.gson.annotations.SerializedName; public class UserDto { + @Nullable + @SerializedName("id") + public String id; @Nullable @SerializedName("name") public String name; @@ -14,7 +17,7 @@ public class UserDto { @SerializedName("lastVisit") public String lastVisit; @Nullable - @SerializedName("photoUrl") + @SerializedName("photo") public String photoUrl; @Nullable @SerializedName("position") diff --git a/app/src/main/java/ru/myitschool/work/data/source/UserApi.java b/app/src/main/java/ru/myitschool/work/data/source/UserApi.java index 04e3bf5..7d74f2b 100644 --- a/app/src/main/java/ru/myitschool/work/data/source/UserApi.java +++ b/app/src/main/java/ru/myitschool/work/data/source/UserApi.java @@ -1,14 +1,19 @@ package ru.myitschool.work.data.source; import retrofit2.Call; +import retrofit2.http.Body; import retrofit2.http.GET; +import retrofit2.http.PATCH; import retrofit2.http.Path; +import ru.myitschool.work.data.dto.QrDto; import ru.myitschool.work.data.dto.UserDto; public interface UserApi { - @GET("user/{login}") + @GET("api/{login}/info") Call getByLogin(@Path("login") String login); - @GET("user/username/{username}") + @GET("api/{login}/auth") Call isExist(@Path("username") String login); + @PATCH("api/{login}/open/") + Call openDoor(@Path("login") String login, @Body QrDto qrDto); } diff --git a/app/src/main/java/ru/myitschool/work/domain/entities/QrEntity.java b/app/src/main/java/ru/myitschool/work/domain/entities/QrEntity.java new file mode 100644 index 0000000..680a404 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entities/QrEntity.java @@ -0,0 +1,27 @@ +package ru.myitschool.work.domain.entities; + +import androidx.annotation.NonNull; + +public class QrEntity { + + @NonNull + private final String login; + + @NonNull + public String getQr() { + return qr; + } + + @NonNull + private final String qr; + + @NonNull + public String getLogin() { + return login; + } + + public QrEntity(@NonNull String login, @NonNull String qr) { + this.login = login; + this.qr = qr; + } +} diff --git a/app/src/main/java/ru/myitschool/work/domain/entities/UserEntity.java b/app/src/main/java/ru/myitschool/work/domain/entities/UserEntity.java index e1835ac..2df0850 100644 --- a/app/src/main/java/ru/myitschool/work/domain/entities/UserEntity.java +++ b/app/src/main/java/ru/myitschool/work/domain/entities/UserEntity.java @@ -6,6 +6,8 @@ import javax.annotation.Nullable; public class UserEntity { + @NonNull + private final String id; @NonNull private final String name; @NonNull @@ -19,7 +21,7 @@ public class UserEntity { @NonNull public String getId() { - return login; + return id; } @Nullable @@ -33,7 +35,7 @@ public class UserEntity { } @Nullable - public String getPhoto() { + public String getPhotoUrl() { return photoUrl; } @@ -42,12 +44,18 @@ public class UserEntity { return position; } + @NonNull + public String getLogin() { + return login; + } + public UserEntity( - @NonNull String login, + @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; diff --git a/app/src/main/java/ru/myitschool/work/domain/login/IsUserExistUseCase.java b/app/src/main/java/ru/myitschool/work/domain/login/IsUserExistUseCase.java index e9eb667..d8d6544 100644 --- a/app/src/main/java/ru/myitschool/work/domain/login/IsUserExistUseCase.java +++ b/app/src/main/java/ru/myitschool/work/domain/login/IsUserExistUseCase.java @@ -16,7 +16,7 @@ public class IsUserExistUseCase { public void execute(@NonNull String login, Consumer> callback) { repository.isUserExist(login, status -> { - boolean isAvailable = status.getStatusCode() == 200 || status.getStatusCode() == 404; + boolean isAvailable = status.getStatusCode() == 200 || status.getStatusCode() == 401; callback.accept( new Status<>( status.getStatusCode(), diff --git a/app/src/main/java/ru/myitschool/work/domain/qr/PushQrUseCase.java b/app/src/main/java/ru/myitschool/work/domain/qr/PushQrUseCase.java new file mode 100644 index 0000000..2dc3554 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/qr/PushQrUseCase.java @@ -0,0 +1,28 @@ +package ru.myitschool.work.domain.qr; + +import androidx.annotation.NonNull; + +import java.util.function.Consumer; + +import ru.myitschool.work.domain.entities.QrEntity; +import ru.myitschool.work.domain.entities.Status; + +public class PushQrUseCase { + private final QrRepository repository; + + public PushQrUseCase(QrRepository repository) { + this.repository = repository; + } + public void execute(@NonNull QrEntity qrEntity, Consumer> callback) { + repository.pushQr(qrEntity, status -> { + boolean isOpened = status.getStatusCode() == 200 || status.getStatusCode() == 401; + callback.accept( + new Status<>( + status.getStatusCode(), + isOpened ? status.getStatusCode() == 200 : null, + status.getErrors() + ) + ); + }); + } +} diff --git a/app/src/main/java/ru/myitschool/work/domain/qr/QrRepository.java b/app/src/main/java/ru/myitschool/work/domain/qr/QrRepository.java new file mode 100644 index 0000000..41136c3 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/qr/QrRepository.java @@ -0,0 +1,13 @@ +package ru.myitschool.work.domain.qr; + +import androidx.annotation.NonNull; + +import java.util.function.Consumer; + +import ru.myitschool.work.domain.entities.QrEntity; +import ru.myitschool.work.domain.entities.Status; + +public interface QrRepository { + + void pushQr(@NonNull QrEntity qrEntity, @NonNull Consumer> callback); +} diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.java b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.java index 0ef9ecd..5b2f1a1 100644 --- a/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.java +++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginFragment.java @@ -1,5 +1,7 @@ package ru.myitschool.work.ui.login; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; import android.text.Editable; import android.view.View; @@ -26,6 +28,16 @@ public class LoginFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + + if (getContext() != null) { + SharedPreferences sharedPreferences = getContext().getSharedPreferences("login", Context.MODE_PRIVATE); + if (sharedPreferences.getString("login", null) != null && getView() != null) { + + Navigation.findNavController(getView()).navigate( + R.id.action_loginFragment_to_userFragment); + } + } + binding = FragmentLoginBinding.bind(view); viewModel = new ViewModelProvider(this).get(LoginViewModel.class); binding.username.addTextChangedListener(new OnChangeText() { @@ -39,6 +51,7 @@ public class LoginFragment extends Fragment { subscribe(viewModel); } + private void subscribe(LoginViewModel viewModel) { viewModel.errorLiveData.observe(getViewLifecycleOwner(), error -> { Toast.makeText(getContext(), error, Toast.LENGTH_SHORT).show(); @@ -47,8 +60,15 @@ public class LoginFragment extends Fragment { binding.login.setClickable(state.isButtonActive()); }); viewModel.openProfileLiveData.observe(getViewLifecycleOwner(), (unused) -> { - View view = getView(); - if (view == null) return; + + if (getContext() != null) { + SharedPreferences sharedPreferences = getContext().getSharedPreferences("login", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("login", binding.username.getText().toString()); + editor.commit(); + } + + if (getView() == null) return; Navigation.findNavController(getView()).navigate( R.id.action_loginFragment_to_userFragment); diff --git a/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.java b/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.java index b4bbf65..420ab5a 100644 --- a/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.java +++ b/app/src/main/java/ru/myitschool/work/ui/login/LoginViewModel.java @@ -1,5 +1,7 @@ package ru.myitschool.work.ui.login; +import android.content.SharedPreferences; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; @@ -53,6 +55,9 @@ public class LoginViewModel extends ViewModel { mutableErrorLiveData.postValue("Something went wrong. Try again later"); return; } + if (status.getStatusCode() == 401) { + mutableErrorLiveData.postValue("Логина не существует или неверный"); + } if (status.getStatusCode() == 200) { mutableOpenProfileLiveData.postValue(null); } diff --git a/app/src/main/java/ru/myitschool/work/ui/profile/UserFragment.java b/app/src/main/java/ru/myitschool/work/ui/profile/UserFragment.java index c33ff85..3e99319 100644 --- a/app/src/main/java/ru/myitschool/work/ui/profile/UserFragment.java +++ b/app/src/main/java/ru/myitschool/work/ui/profile/UserFragment.java @@ -1,24 +1,27 @@ package ru.myitschool.work.ui.profile; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; 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; import com.squareup.picasso.Picasso; import ru.myitschool.work.R; import ru.myitschool.work.databinding.FragmentUserBinding; import ru.myitschool.work.domain.entities.UserEntity; +import ru.myitschool.work.ui.qr.scan.QrScanDestination; import ru.myitschool.work.utils.Utils; public class UserFragment extends Fragment { - private static final String KEY_ID = "id"; - private FragmentUserBinding binding; private UserViewModel viewModel; @@ -31,13 +34,44 @@ public class UserFragment extends Fragment { super.onViewCreated(view, savedInstanceState); binding = FragmentUserBinding.bind(view); + binding.scan.setOnClickListener(v -> { + if (getView() != null) { + Navigation.findNavController(getView()).navigate( + R.id.action_userFragment_to_qrScanFragment); + } + }); + + getParentFragmentManager().setFragmentResultListener(QrScanDestination.REQUEST_KEY, this, new FragmentResultListener() { + @Override + public void onFragmentResult(@NonNull String key, @NonNull Bundle bundle) { + + if (getView() != null) { + Navigation.findNavController(getView()).navigate( + R.id.action_userFragment_to_qrResultFragment); + } + } + }); + + binding.logout.setOnClickListener(v -> { + if (getContext() != null) { + SharedPreferences sharedPreferences = getContext().getSharedPreferences("login", Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("login", null); + editor.commit(); + } + if (getView() != null) { + Navigation.findNavController(getView()).navigate( + R.id.action_userFragment_to_loginFragment); + } + }); + viewModel = new ViewModelProvider(this).get(UserViewModel.class); viewModel.stateLiveData.observe(getViewLifecycleOwner(), state -> { UserEntity entity = state.getItem(); if (entity == null) { return; } else { - binding.photo.setVisibility(Utils.visibleOrGone(entity.getPhoto() != 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)); @@ -45,15 +79,20 @@ public class UserFragment extends Fragment { binding.position.setText(entity.getPosition()); binding.lastEntry.setText(entity.getLast_visit()); - if (entity.getPhoto() != null) { - Picasso.get().load(entity.getPhoto()).into(binding.photo); + if (entity.getPhotoUrl() != null) { + Picasso.get().load(entity.getPhotoUrl()).into(binding.photo); } } }); - String id = getArguments() != null ? getArguments().getString(KEY_ID) : null; - if (id == null) throw new IllegalStateException("ID is null"); - viewModel.update(id); + if (getContext() != null) { + SharedPreferences sharedPreferences = getContext().getSharedPreferences("login", Context.MODE_PRIVATE); + String login = sharedPreferences.getString("login", null); + if (login != null && getView() != null) { + viewModel.update(login); + } + } + } @@ -63,9 +102,4 @@ public class UserFragment extends Fragment { super.onDestroyView(); } - public static Bundle getBundle(@NonNull String id) { - Bundle bundle = new Bundle(); - bundle.putString(KEY_ID, id); - return bundle; - } } diff --git a/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.java b/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.java index 4e2ab7a..a988427 100644 --- a/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.java +++ b/app/src/main/java/ru/myitschool/work/ui/profile/UserViewModel.java @@ -22,13 +22,13 @@ public class UserViewModel extends ViewModel { - public UserViewModel(String id) { - update(id); + public UserViewModel(String login) { + update(login); } - public void update(@NonNull String id) { + public void update(@NonNull String login) { mutableStateLiveData.setValue(new State(null, null, true)); - getUserByLoginUseCase.execute(id, status -> { + getUserByLoginUseCase.execute(login, status -> { mutableStateLiveData.postValue(fromStatus(status)); }); } diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.java b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.java new file mode 100644 index 0000000..e868ed9 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultFragment.java @@ -0,0 +1,73 @@ +package ru.myitschool.work.ui.qr.result; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.View; +import android.widget.Toast; + +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; + +import ru.myitschool.work.R; +import ru.myitschool.work.databinding.FragmentQrResultBinding; +import ru.myitschool.work.ui.qr.scan.QrScanDestination; + +public class QrResultFragment extends Fragment { + + private FragmentQrResultBinding binding; + private String resultQr; + private QrResultViewModel viewModel; + + public QrResultFragment() { + super(R.layout.fragment_qr_result); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding = FragmentQrResultBinding.bind(view); + + binding.close.setOnClickListener(v -> { + if (getView() != null) { + Navigation.findNavController(getView()).navigate( + R.id.action_qrResultFragment_to_userFragment); + } + }); + + getParentFragmentManager().setFragmentResultListener(QrScanDestination.REQUEST_KEY, this, new FragmentResultListener() { + @Override + public void onFragmentResult(@NonNull String key, @NonNull Bundle bundle) { + resultQr = bundle.getString(QrScanDestination.REQUEST_KEY); + } + }); + + viewModel = new ViewModelProvider(this).get(QrResultViewModel.class); + viewModel.stateLiveData.observe(getViewLifecycleOwner(), state -> { + if (state.getErrorMessage() == null && state.isOpened()) { + binding.result.setText(R.string.door_opened); + } else { + binding.result.setText(R.string.error); + Toast.makeText(getContext(), state.getErrorMessage(), Toast.LENGTH_SHORT).show(); + } + }); + + if (getContext() != null) { + SharedPreferences sharedPreferences = getContext().getSharedPreferences("login", Context.MODE_PRIVATE); + String login = sharedPreferences.getString("login", null); + if (login != null && getView() != null) { + viewModel.update(login, resultQr); + } + } + } + + @Override + public void onDestroy() { + binding = null; + super.onDestroy(); + } +} diff --git a/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.java b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.java new file mode 100644 index 0000000..b7f0491 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/qr/result/QrResultViewModel.java @@ -0,0 +1,58 @@ +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 ru.myitschool.work.data.UserRepositoryImplementation; +import ru.myitschool.work.domain.entities.QrEntity; +import ru.myitschool.work.domain.entities.Status; +import ru.myitschool.work.domain.qr.PushQrUseCase; +import ru.myitschool.work.ui.profile.UserViewModel; + +public class QrResultViewModel extends ViewModel { + private final MutableLiveData mutableStateLiveData = new MutableLiveData( + new State(null, false) + ); + public final LiveData stateLiveData = mutableStateLiveData; + + public final PushQrUseCase pushQrUseCase = new PushQrUseCase( + UserRepositoryImplementation.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; + } + } +} diff --git a/app/src/main/res/layout/fragment_qr_result.xml b/app/src/main/res/layout/fragment_qr_result.xml new file mode 100644 index 0000000..41a0884 --- /dev/null +++ b/app/src/main/res/layout/fragment_qr_result.xml @@ -0,0 +1,36 @@ + + + + + +