version2
This commit is contained in:
parent
1b33ecc800
commit
dc426bf75a
@ -10,12 +10,12 @@ val packageName = "ru.myitschool.work"
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = packageName
|
namespace = packageName
|
||||||
compileSdk = Version.Android.Sdk.compile
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = packageName
|
applicationId = packageName
|
||||||
minSdk = Version.Android.Sdk.min
|
minSdk = 35
|
||||||
targetSdk = Version.Android.Sdk.target
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools" >
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera.any" />
|
<uses-feature android:name="android.hardware.camera.any" />
|
||||||
|
|
||||||
@ -8,7 +8,6 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
@ -18,16 +17,13 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Default"
|
android:theme="@style/Theme.Default"
|
||||||
tools:targetApi="31" >
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.login.LoginActivity"
|
android:name=".ui.login.LoginActivity"
|
||||||
android:exported="false" />
|
android:exported="true">
|
||||||
<activity
|
|
||||||
android:name=".ui.login.LoginActivity"
|
|
||||||
android:exported="true" >
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
115
app/src/main/java/ru/myitschool/work/ui/login/LoginActivity.java
Normal file
115
app/src/main/java/ru/myitschool/work/ui/login/LoginActivity.java
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package ru.myitschool.work.ui.login;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.activity.EdgeToEdge;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.graphics.Insets;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import com.example.myapplication.core.SettingConstants;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
|
|
||||||
|
import ru.myitschool.work.R;
|
||||||
|
import ru.myitschool.work.databinding.ActivityLoginBinding;
|
||||||
|
import ru.myitschool.work.ui.main.MainActivity;
|
||||||
|
|
||||||
|
public class LoginActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private ActivityLoginBinding binding;
|
||||||
|
private LoginViewModel viewModel;
|
||||||
|
|
||||||
|
|
||||||
|
public LoginActivity() {
|
||||||
|
super(R.layout.activity_login);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
EdgeToEdge.enable(this);
|
||||||
|
setContentView(R.layout.activity_login);
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||||
|
return insets;
|
||||||
|
|
||||||
|
binding = ActivityLoginBinding.bind(v);
|
||||||
|
viewModel = new ViewModelProvider(this).get(ActivityLoginBinding.class);
|
||||||
|
|
||||||
|
binding.email.addTextChangedListener(new TextChangedListener<>(binding.email) {
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(TextInputEditText target, Editable s) {
|
||||||
|
listenerEmailEditText(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.password.addTextChangedListener(new TextChangedListener<>(binding.password) {
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(TextInputEditText target, Editable s) {
|
||||||
|
listenerPasswordEditText(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.btEnter.setOnClickListener(this.onClickListenerLoginButton);
|
||||||
|
|
||||||
|
subscribe();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void subscribe() {
|
||||||
|
viewModel.errorLiveData.observe(getViewLifecycleOwner(), error -> {
|
||||||
|
binding.btEnter.setEnabled(true);
|
||||||
|
Snackbar.make(requireView(), error, Snackbar.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
viewModel.openLiveData.observe(getViewLifecycleOwner(), employee -> {
|
||||||
|
binding.btEnter.setEnabled(true);
|
||||||
|
|
||||||
|
SharedPreferences settings = requireView().getContext().getSharedPreferences(
|
||||||
|
SettingConstants.PREFS_FILE, Context.MODE_PRIVATE
|
||||||
|
);
|
||||||
|
settings.edit().putLong(SettingConstants.PREF_ID, employee.getId()).apply();
|
||||||
|
settings.edit().putString(SettingConstants.PREF_ROLE, employee.getRole()).apply();
|
||||||
|
|
||||||
|
startActivity(new Intent(getApplicationContext(), MainActivity.class));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listenerEmailEditText(Editable s) {
|
||||||
|
if (s.toString().isEmpty()) {
|
||||||
|
binding.email.setError("Обязательное поле");
|
||||||
|
} else if (!isEmailValid(s.toString())) {
|
||||||
|
binding.emailLay.setError("Неверный формат");
|
||||||
|
} else {
|
||||||
|
binding.emailLay.setErrorEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listenerPasswordEditText(Editable s) {
|
||||||
|
if (s.toString().isEmpty()) {
|
||||||
|
binding.passwordLay.setError("Обязательное поле");
|
||||||
|
} else {
|
||||||
|
binding.passwordLay.setErrorEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClickListenerLoginButton(View view) {
|
||||||
|
binding.btEnter.setEnabled(false);
|
||||||
|
|
||||||
|
viewModel.changeEmail(String.valueOf(binding.email.getText()));
|
||||||
|
viewModel.changePassword(String.valueOf(binding.password.getText()));
|
||||||
|
viewModel.confirm();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEmailValid(String email) {
|
||||||
|
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package ru.myitschool.work.ui.login;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import ru.myitschool.work.api.data.SignRepositoryImpl;
|
||||||
|
import ru.myitschool.work.api.domain.entity.employee.EmpolyeeEntity;
|
||||||
|
import ru.myitschool.work.api.domain.useCases.sign.LoginEmployeeUseCase;
|
||||||
|
|
||||||
|
public class LoginViewModel {
|
||||||
|
private final MutableLiveData<String> mutableErrorLiveData = new MutableLiveData<>();
|
||||||
|
public final LiveData<String> errorLiveData = mutableErrorLiveData;
|
||||||
|
|
||||||
|
private final MutableLiveData<EmpolyeeEntity> mutableOpenLiveData = new MutableLiveData<>();
|
||||||
|
public final LiveData<EmpolyeeEntity> openLiveData = mutableOpenLiveData;
|
||||||
|
|
||||||
|
private final LoginEmployeeUseCase loginEmployeeUseCase = new LoginEmployeeUseCase(
|
||||||
|
SignRepositoryImpl.getInstance()
|
||||||
|
);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String email;
|
||||||
|
@Nullable
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public void changeEmail(@NonNull String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changePassword(@NonNull String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void confirm() {
|
||||||
|
final String currentEmail = email;
|
||||||
|
final String currentPassword = password;
|
||||||
|
|
||||||
|
if (currentEmail == null || currentEmail.isEmpty()) {
|
||||||
|
mutableErrorLiveData.postValue("Пароль пустой!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentPassword == null || currentPassword.isEmpty()) {
|
||||||
|
mutableErrorLiveData.postValue("email пустой!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loginEmployeeUseCase.execute(currentEmail, currentPassword, status -> {
|
||||||
|
if (status.getStatusCode() == 200 && status.getErrors() == null && status.getValue() != null) {
|
||||||
|
mutableOpenLiveData.postValue(status.getValue());
|
||||||
|
} else if (status.getStatusCode() == 401) mutableErrorLiveData.postValue("Данные не верны. Попробуйте ещё разок :(");
|
||||||
|
else mutableErrorLiveData.postValue("Вы не подключены к интернету :(");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package ru.myitschool.work.ui.main;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.activity.EdgeToEdge;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.graphics.Insets;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
|
||||||
|
import ru.myitschool.work.R;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
EdgeToEdge.enable(this);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||||
|
return insets;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
package ru.myitschool.work.ui.qr.scan
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
|
|
||||||
@Serializable
|
|
||||||
data object QrScanDestination {
|
|
||||||
const val REQUEST_KEY = "qr_result"
|
|
||||||
private const val KEY_QR_DATA = "key_qr"
|
|
||||||
|
|
||||||
fun newInstance(): QrScanFragment {
|
|
||||||
return QrScanFragment()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDataIfExist(bundle: Bundle): String? {
|
|
||||||
return if (bundle.containsKey(KEY_QR_DATA)) {
|
|
||||||
bundle.getString(KEY_QR_DATA)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun packToBundle(data: String): Bundle {
|
|
||||||
return bundleOf(
|
|
||||||
KEY_QR_DATA to data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
package ru.myitschool.work.ui.qr.scan
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.camera.core.ImageAnalysis
|
|
||||||
import androidx.camera.mlkit.vision.MlKitAnalyzer
|
|
||||||
import androidx.camera.view.LifecycleCameraController
|
|
||||||
import androidx.camera.view.PreviewView
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.setFragmentResult
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import androidx.navigation.fragment.findNavController
|
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanner
|
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
|
||||||
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.visibleOrGone
|
|
||||||
|
|
||||||
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
|
|
||||||
class QrScanFragment : Fragment(R.layout.fragment_qr_scan) {
|
|
||||||
private var _binding: FragmentQrScanBinding? = null
|
|
||||||
private val binding: FragmentQrScanBinding get() = _binding!!
|
|
||||||
|
|
||||||
private var barcodeScanner: BarcodeScanner? = null
|
|
||||||
private var isCameraInit: Boolean = false
|
|
||||||
private val permissionLauncher = registerForActivityResult(
|
|
||||||
ActivityResultContracts.RequestPermission()
|
|
||||||
) { isGranted -> viewModel.onPermissionResult(isGranted) }
|
|
||||||
|
|
||||||
private val viewModel: QrScanViewModel by viewModels()
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
_binding = FragmentQrScanBinding.bind(view)
|
|
||||||
sendResult(bundleOf())
|
|
||||||
subscribe()
|
|
||||||
initCallback()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initCallback() {
|
|
||||||
binding.close.setOnClickListener { viewModel.close() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun subscribe() {
|
|
||||||
viewModel.state.collectWhenStarted(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) {
|
|
||||||
startCamera()
|
|
||||||
isCameraInit = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.action.collectWhenStarted(this) { action ->
|
|
||||||
when (action) {
|
|
||||||
is QrScanViewModel.Action.RequestPermission -> requestPermission(action.permission)
|
|
||||||
is QrScanViewModel.Action.CloseWithCancel -> {
|
|
||||||
goBack()
|
|
||||||
}
|
|
||||||
is QrScanViewModel.Action.CloseWithResult -> {
|
|
||||||
sendResult(QrScanDestination.packToBundle(action.result))
|
|
||||||
goBack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requestPermission(permission: String) {
|
|
||||||
permissionLauncher.launch(permission)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startCamera() {
|
|
||||||
val context = requireContext()
|
|
||||||
val cameraController = LifecycleCameraController(context)
|
|
||||||
val previewView: PreviewView = binding.viewFinder
|
|
||||||
val executor = ContextCompat.getMainExecutor(context)
|
|
||||||
|
|
||||||
val options = BarcodeScannerOptions.Builder()
|
|
||||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
|
||||||
.build()
|
|
||||||
val barcodeScanner = BarcodeScanning.getClient(options)
|
|
||||||
this.barcodeScanner = barcodeScanner
|
|
||||||
|
|
||||||
cameraController.setImageAnalysisAnalyzer(
|
|
||||||
executor,
|
|
||||||
MlKitAnalyzer(
|
|
||||||
listOf(barcodeScanner),
|
|
||||||
ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED,
|
|
||||||
executor
|
|
||||||
) { result ->
|
|
||||||
result?.getValue(barcodeScanner)?.firstOrNull()?.let { value ->
|
|
||||||
viewModel.findBarcode(value)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
cameraController.bindToLifecycle(this)
|
|
||||||
previewView.controller = cameraController
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
barcodeScanner?.close()
|
|
||||||
barcodeScanner = null
|
|
||||||
_binding = null
|
|
||||||
super.onDestroyView()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun goBack() {
|
|
||||||
findNavControllerOrNull()?.popBackStack()
|
|
||||||
?: requireActivity().onBackPressedDispatcher.onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendResult(bundle: Bundle) {
|
|
||||||
setFragmentResult(
|
|
||||||
QrScanDestination.REQUEST_KEY,
|
|
||||||
bundle
|
|
||||||
)
|
|
||||||
findNavControllerOrNull()
|
|
||||||
?.previousBackStackEntry
|
|
||||||
?.savedStateHandle
|
|
||||||
?.set(QrScanDestination.REQUEST_KEY, bundle)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findNavControllerOrNull(): NavController? {
|
|
||||||
return try {
|
|
||||||
findNavController()
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
package ru.myitschool.work.ui.qr.scan
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.google.mlkit.vision.barcode.common.Barcode
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
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
|
|
||||||
|
|
||||||
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
|
|
||||||
class QrScanViewModel(
|
|
||||||
application: Application
|
|
||||||
) : AndroidViewModel(application) {
|
|
||||||
|
|
||||||
private val _action = MutablePublishFlow<Action>()
|
|
||||||
val action = _action.asSharedFlow()
|
|
||||||
|
|
||||||
private val _state = MutableStateFlow<State>(initialState)
|
|
||||||
val state = _state.asStateFlow()
|
|
||||||
|
|
||||||
init {
|
|
||||||
checkPermission()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onPermissionResult(isGranted: Boolean) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
if (isGranted) {
|
|
||||||
_state.update { State.Scan }
|
|
||||||
} else {
|
|
||||||
_action.emit(Action.CloseWithCancel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkPermission() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
val isPermissionGranted = ContextCompat.checkSelfPermission(
|
|
||||||
getApplication(),
|
|
||||||
CAMERA_PERMISSION
|
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
if (isPermissionGranted) {
|
|
||||||
_state.update { State.Scan }
|
|
||||||
} else {
|
|
||||||
delay(1000)
|
|
||||||
_action.emit(Action.RequestPermission(CAMERA_PERMISSION))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findBarcode(barcode: Barcode) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
barcode.rawValue?.let { value ->
|
|
||||||
_action.emit(Action.CloseWithResult(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun close() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
_action.emit(Action.CloseWithCancel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface State {
|
|
||||||
data object Loading : State
|
|
||||||
|
|
||||||
data object Scan : State
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface Action {
|
|
||||||
data class RequestPermission(
|
|
||||||
val permission: String
|
|
||||||
) : Action
|
|
||||||
data object CloseWithCancel : Action
|
|
||||||
data class CloseWithResult(
|
|
||||||
val result: String
|
|
||||||
) : Action
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
val initialState = State.Loading
|
|
||||||
|
|
||||||
const val CAMERA_PERMISSION = Manifest.permission.CAMERA
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,30 @@
|
|||||||
|
package ru.myitschool.work.utils;
|
||||||
|
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
|
||||||
|
public abstract class TextChangedListener<T> implements TextWatcher {
|
||||||
|
private final T target;
|
||||||
|
|
||||||
|
public TextChangedListener(T target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onTextChanged(T target, Editable s);
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
BIN
app/src/main/res/drawable/account.png
Normal file
BIN
app/src/main/res/drawable/account.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 433 B |
BIN
app/src/main/res/drawable/background.png
Normal file
BIN
app/src/main/res/drawable/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/main/res/drawable/management.png
Normal file
BIN
app/src/main/res/drawable/management.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 535 B |
BIN
app/src/main/res/drawable/office.png
Normal file
BIN
app/src/main/res/drawable/office.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 387 B |
BIN
app/src/main/res/drawable/scanqr.png
Normal file
BIN
app/src/main/res/drawable/scanqr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 409 B |
96
app/src/main/res/layout/activity_login.xml
Normal file
96
app/src/main/res/layout/activity_login.xml
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout 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:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.login.LoginActivity"
|
||||||
|
android:background="@drawable/background">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_marginStart="40dp"
|
||||||
|
android:layout_marginTop="220dp"
|
||||||
|
android:layout_marginEnd="50dp"
|
||||||
|
android:layout_marginBottom="270dp"
|
||||||
|
app:cardBackgroundColor="@color/white">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="104dp"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="115dp"
|
||||||
|
android:layout_marginTop="40dp"
|
||||||
|
android:layout_marginEnd="115dp"
|
||||||
|
android:text="Вход"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="40dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/email_lay"
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="80dp"
|
||||||
|
android:layout_marginTop="110dp"
|
||||||
|
android:layout_marginEnd="80dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/email"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:hint="Email"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/password_lay"
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="80dp"
|
||||||
|
android:layout_marginTop="180dp"
|
||||||
|
android:layout_marginEnd="80dp">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/password"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:hint="Пароль"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/bt_enter"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Войти в систему"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="80dp"
|
||||||
|
android:layout_marginTop="250dp"
|
||||||
|
android:layout_marginEnd="80dp"
|
||||||
|
app:backgroundTint="#B1B1CF"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
|
||||||
|
</RelativeLayout>
|
36
app/src/main/res/layout/activity_main.xml
Normal file
36
app/src/main/res/layout/activity_main.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="79dp"
|
||||||
|
android:layout_height="131dp"
|
||||||
|
android:layout_above="@+id/bottom_navigation_view"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginEnd="0dp"
|
||||||
|
android:layout_marginBottom="0dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
android:id="@+id/bottom_navigation_view"
|
||||||
|
style="@style/Widget.Material3.ActionMode"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
|
app:itemIconSize="30dp"
|
||||||
|
app:labelVisibilityMode="unlabeled"
|
||||||
|
app:menu="@menu/bottom_nav_menu" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
115
app/src/main/res/layout/fragment_main.xml
Normal file
115
app/src/main/res/layout/fragment_main.xml
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/main_fragment">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/photo"
|
||||||
|
android:layout_width="160dp"
|
||||||
|
android:layout_height="160dp"
|
||||||
|
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:contentDescription="@string/content_description" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fullname"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_toEndOf="@+id/photo"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/position"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
|
||||||
|
android:layout_below="@+id/fullname"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
|
||||||
|
android:layout_toEndOf="@+id/photo"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/lastEntry"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
|
||||||
|
android:layout_below="@+id/position"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
|
||||||
|
android:layout_toEndOf="@+id/photo"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/refresh"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:layout_below="@+id/lastEntry"
|
||||||
|
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_toEndOf="@+id/photo"
|
||||||
|
android:text="@string/refresh" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/logout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:layout_below="@+id/photo"
|
||||||
|
android:layout_alignEnd="@+id/photo"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:text="@string/logout_text" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/scan"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:layout_alignTop="@+id/logout"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_toEndOf="@+id/logout"
|
||||||
|
android:text="@string/scan_text" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/error"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/error_message_root_activity"
|
||||||
|
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:padding="5dp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
30
app/src/main/res/menu/bottom_nav_menu.xml
Normal file
30
app/src/main/res/menu/bottom_nav_menu.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/employees"
|
||||||
|
android:title="@string/employees"
|
||||||
|
android:icon="@drawable/management"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/profile"
|
||||||
|
android:title="@string/profile"
|
||||||
|
android:icon="@drawable/account"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/scaner"
|
||||||
|
android:title="@string/scaning"
|
||||||
|
android:icon="@drawable/scanqr"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/office"
|
||||||
|
android:title="@string/office"
|
||||||
|
android:icon="@drawable/office"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</menu>
|
@ -19,4 +19,8 @@
|
|||||||
<string name="cancelled">Вход был отменён</string>
|
<string name="cancelled">Вход был отменён</string>
|
||||||
<string name="success">Успешно</string>
|
<string name="success">Успешно</string>
|
||||||
<string name="wrong">Что-то пошло не так</string>
|
<string name="wrong">Что-то пошло не так</string>
|
||||||
|
<string name="employees">Сотрудники</string>
|
||||||
|
<string name="profile">Профиль</string>
|
||||||
|
<string name="office">Офис</string>
|
||||||
|
<string name="scaning">Сканировать</string>
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user