send figma

This commit is contained in:
mister 2025-02-20 14:43:59 +03:00
parent 6b2538c7f8
commit 2c4c6ecd0d
28 changed files with 602 additions and 172 deletions

122
README.md
View File

@ -1,121 +1 @@
[![Android Studio version](https://img.shields.io/endpoint?url=https%3A%2F%2Fsicampus.ru%2Fgitea%2Fcore%2Fdocs%2Fraw%2Fbranch%2Fmain%2Fandroid-studio-label.json)](https://sicampus.ru/gitea/core/docs/src/branch/main/how-upload-project.md)
# НТО 2024. II отборочный этап. Командные задани — клиентская часть
## 📖 Предыстория
В компании S контроль доступа в офис осуществляется с помощью СКУД (системы контроля управления доступом). На данный момент у каждого сотрудника компании есть карта-пропуск с NFC меткой. А у каждой входной двери есть считыватель с обеих сторон. При поднесении карты к считывателю, дверь открывается, а информация о времени входа или выхода сотрудника фиксируется в базе данных.
Администрации компании S требуется мобильное приложение, как для рядовых сотрудников, так и для администрации с возможностью просмотра посещений и работой электронного пропуска как временной замены обычного (при помощи сканировании QR кода, который находится на считывателе). Поскольку в приложении есть возможность использовать телефон как пропуск - то к данному приложению повышенные требования к безопасности всех данных, находящихся внутри него.
## 📋 Системные требования
| **Параметр** | **Требование** |
|-----------------------------|---------------------------------------|
| **Минимальная версия Android** | 9.0 (API 28) |
| **Целевая версия Android** | 14 (API 34) |
| **Поддерживаемые устройства** | смартфоны, планшеты |
| **Ориентация экранов** | портретная |
| **Языки** | русский, английский |
| **Разрешения** | доступ к интернету, камера (при необходимости) |
## 📱 Техническое задание
Требуется разработать нативное мобильное приложение, которое будет содержать следующие экраны.
### 1. Экран авторизации
> Данный экран должен быть показан при первом входе в приложение, а также в ситуациях, когда пользователь не зарегистрировался в приложении.
#### Элементы, которые должны присутствовать на экране:
- Поле ввода (`id/username`), в котором пользователю необходимо ввести свой логин.
- Кнопка входа (`id/login`), по нажатию на которую пользователь авторизуется в системе.
- По умолчанию скрытое текстовое поле с ошибкой (`id/error`).
#### Требования к компонентам:
1. В пустом поле ввода должна отображаться подсказка, что требуется ввести пользователю.
2. Если хотя бы одно из условий ниже соблюдено - кнопка должна быть неактивной:
- Поле ввода пустое.
- Количество символов менее 3х.
- Логин начинается с цифры.
- Логин содержит символы, отличные от латинского алфавита и цифр.
3. Поле ввода и кнопку должно быть видно при раскрытии клавиатуры.
4. - При нажатии на кнопку входа необходимо проверить, что данный пользователь существует с помощью запроса `api/<LOGIN>/auth` (подробное описание представлено в техническом задании серверной части).
5. В случае отсутствия логина или любой другой неполадки - необходимо вывести ошибку, пока пользователь не изменит текстовое поле или повторно не нажмёт на кнопку.
6. После нажатия на кнопку - логин должен быть сохранён и при следующем открытии приложения экран авторизации не должен быть показан.
7. После нажатия на кнопку - при нажатии стрелки назад - экран авторизации не должен быть показан повторно.
8. Экран авторизации показывается только в случае, если пользователь неавторизован.
### 2. Главный экран
> Данный экран содержит общую информацию о пользователе:
>- ФИО
>- Фото
>- Должность
>- Время последнего входа
#### Элементы, которые должны присутствовать на экране:
- Текстовое поле (`id/fullname`), в котором написано имя пользователя.
- Изображение (`id/photo`), на котором отображено фото пользователя.
- Текстовое поле (`id/position`), в котором написана должность пользователя.
- Текстовое поле (`id/lastEntry`), в котором написана дата и время последнего входа пользователя.
- Кнопка (`id/logout`) для выхода пользователя из аккаунта.
- Кнопка (`id/refresh`) для обновления данных.
- Кнопка (`id/scan`) для сканирования QR кода.
- По умолчанию скрытое текстовое поле с ошибкой (`id/error`).
#### Требования к компонентам:
- В случае любой ошибки необходимо скрыть все элементы, кроме текстового поля с ошибкой и кнопки обновления данных.
- Для получения данных необходимо использовать сетевой запрос `/api/<LOGIN>/info`.
- Формат даты и времени последнего входа пользователя: `yyyy-MM-dd HH:mm` (например: 2024-02-31 08:31). Время необходимо отображать с сервера, без поправок на часовой пояс или локальное смещение.
- При нажатии на кнопку выход все данные (если есть) пользователя должны быть очищены, а приложение должно открыть экран авторизации.
- При нажатии кнопки сканирования необходимо открыть экран сканирования QR кода.
- При нажатии на кнопку обновления данных - необходимо повторно вызывать сетевой запрос для получения актуальных данных.
### 3. Экран сканирования QR-кода
> Данный экран позволяет отсканировать код на турникете и войти с помощью смартфона. В данном случае данный экран будет уже написан и представлен dам в готовом виде в заготовке. Вам необходимо только подписаться на его результат с помощью **Result API** и обработать считанные данные из QR кода. **Данный экран нельзя модифицировать. Он поставляется как есть.**
### 4. Экран с результатом сканирования QR кода
> На данном экране необходимо вывести успешность или неуспешность входа с помощью метода QR кода.
#### Элементы, которые должны присутствовать на экране:
- Текстовое поле (`id/result`), где содержится текст об успешности или неуспешности входа.
- Кнопка (`id/close`) для закрытия данного экрана.
#### Требования к компонентам:
- В случае, если результат пришёл пустым или со статусом “Отмена” - необходимо вывести пользователю текст:
*"Вход был отменён/Operation was cancelled"*
- В случае, если данные пришли, то необходимо их отправить на сервер с запросом `api/<LOGIN>/open`, добавив данные из результата и получить ответ.
- Если сервер ответил успешно - то отображаем текст:
*"Успешно/Success"*
- Если сервер ответил любой ошибкой - то отображаем текст:
*"Что-то пошло не так/Something wrong"*
- Кнопка закрытия всегда открывает главный экран.
## 🛠 Решение
Необходимо загрузить свое решение в систему [по ссылке](https://innovationcampus.ru/lms/mod/quiz/view.php?id=2149).
Отметим, что работу необходимо осуществлять в представленных проектах-заготовках (шаблонах).
## ✅ Особенности оценивания
Оценивание происходит с помощью автоматической системы тестирования, которая в автоматическом режиме находит элементы и взаимодействует с ними (именно для этого у каждого элемента указан уникальный идентификатор, по которому будет производится поиск). Каждый тест происходит с чистой установки приложения.
В случае тестирования сервера на него поочередно отправляются команды, описанные в API и ожидаются определенные корректные ответы.
Сервер и приложение тестируются независимо.
https://www.figma.com/design/m7vk9KAi8KYr8oPF1lB3Dg/Untitled?node-id=2-263&m=dev&t=CBaQLwRrOyOK2s2Z-1

View File

@ -0,0 +1,43 @@
package ru.myitschool.work.data.dto;
import java.time.LocalDateTime;
public class EmployeeDataDto {
private String name;
private String photo;
private String employeePosition;
public LocalDateTime getLastVisit() {
return lastVisit;
}
public void setLastVisit(LocalDateTime lastVisit) {
this.lastVisit = lastVisit;
}
public String getEmployeePosition() {
return employeePosition;
}
public void setEmployeePosition(String employeePosition) {
this.employeePosition = employeePosition;
}
public String getPhoto() {
return photo;
}
public void setPhoto(String photo) {
this.photo = photo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private LocalDateTime lastVisit;
}

View File

@ -0,0 +1,9 @@
package ru.myitschool.work.data.dto;
import java.time.LocalDateTime;
public class EntryDto {
private long codeId;
private LocalDateTime entryTime;
private boolean isCard;
}

View File

@ -0,0 +1,19 @@
package ru.myitschool.work.data.network.api;
import com.google.android.gms.common.internal.safeparcel.SafeParcelable;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Query;
import ru.myitschool.work.data.dto.Code;
import ru.myitschool.work.data.dto.Employee;
public interface AdminApi {
@POST("/api/admin/am-i-admin")
Call<Void> amIAdmin(@Header("Authorization") String auth);
@POST("/api/admin/panel/get-employee-info")
Call<Void> getEmployeeInfo(@Header("Authorization") String auth, @Query("employee-login") String login);
}

View File

@ -3,18 +3,20 @@ package ru.myitschool.work.data.network.api;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.PATCH;
import retrofit2.http.POST;
import retrofit2.http.Path;
import ru.myitschool.work.data.dto.Code;
import ru.myitschool.work.data.dto.Employee;
public interface EmployeeApi {
@GET("/api/{login}/auth")
Call<Void> auth(@Path("login") String login);
@POST("/api/employee/auth")
Call<Void> auth(@Header("Authorization") String auth);
@GET("/api/{login}/info")
Call<Employee> info(@Path("login") String login);
@POST("/api/employee/info")
Call<Employee> info(@Header("Authorization") String auth);
@PATCH("/api/{login}/open")
Call<Void> open(@Path("login") String login, @Body Code code);
@POST("/api/employee/open")
Call<Void> open(@Header("Authorization") String auth, @Body Code code);
}

View File

@ -26,8 +26,8 @@ public class LoginRepositoryImpl implements LoginRepository {
private EmployeeApi employeeApi = RetrofitFactory.getInstance().getEmployeeApi();
@Override
public void auth(@NonNull String login, @NonNull Consumer<Status<Void>> callback) {
employeeApi.auth(login).enqueue(new Callback<Void>() {
public void auth(@NonNull String auth, @NonNull Consumer<Status<Void>> callback) {
employeeApi.auth(auth).enqueue(new Callback<Void>() {
@Override
public void onResponse(Call<Void> call, Response<Void> response) {
callback.accept(new Status<>(response.code(), null, null));

View File

@ -0,0 +1,18 @@
package ru.myitschool.work.domain.admin;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.function.Consumer;
import ru.myitschool.work.data.dto.EmployeeDataDto;
import ru.myitschool.work.data.dto.EntryDto;
import ru.myitschool.work.domain.entities.Status;
public interface AdminRepository {
void amIAdmin(@NonNull String auth, @NonNull Consumer<Status<Void>> callback);
void getEmployeeInfo(@NonNull String auth, @NonNull String login, @NonNull Consumer<Status<EmployeeDataDto>> callback);
void setBlockCondition(@NonNull String auth, @NonNull String login, @NonNull Boolean condition, @NonNull Consumer<Status<Void>> callback);
void getEmployeeEntryList(@NonNull String auth, @NonNull String login, @NonNull Consumer<Status<List<EntryDto>>> callback);
void isEmployeeBlock(@NonNull String auth, @NonNull String login, @NonNull Consumer<Status<Boolean>> callback);
}

View File

@ -0,0 +1,20 @@
package ru.myitschool.work.domain.admin;
import androidx.annotation.NonNull;
import java.util.function.Consumer;
import ru.myitschool.work.data.dto.Employee;
import ru.myitschool.work.domain.entities.Status;
public class AmIAdminUseCase {
private final AdminRepository repository;
public AmIAdminUseCase(AdminRepository repository) {
this.repository = repository;
}
public void execute(@NonNull String auth, @NonNull Consumer<Status<Void>> callback) {
repository.amIAdmin(auth, callback);
}
}

View File

@ -0,0 +1,21 @@
package ru.myitschool.work.domain.admin;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.function.Consumer;
import ru.myitschool.work.data.dto.EntryDto;
import ru.myitschool.work.domain.entities.Status;
public class GetEmployeeEntryListUseCase {
private final AdminRepository repository;
public GetEmployeeEntryListUseCase(AdminRepository repository) {
this.repository = repository;
}
public void execute(@NonNull String auth, @NonNull String login, @NonNull Consumer<Status<List<EntryDto>>> callback) {
repository.getEmployeeEntryList(auth, login, callback);
}
}

View File

@ -0,0 +1,20 @@
package ru.myitschool.work.domain.admin;
import androidx.annotation.NonNull;
import java.util.function.Consumer;
import ru.myitschool.work.data.dto.EmployeeDataDto;
import ru.myitschool.work.domain.entities.Status;
public class GetEmployeeInfoUseCase {
private final AdminRepository repository;
public GetEmployeeInfoUseCase(AdminRepository repository) {
this.repository = repository;
}
public void execute(@NonNull String auth, @NonNull String login, @NonNull Consumer<Status<EmployeeDataDto>> callback) {
repository.getEmployeeInfo(auth, login, callback);
}
}

View File

@ -0,0 +1,19 @@
package ru.myitschool.work.domain.admin;
import androidx.annotation.NonNull;
import java.util.function.Consumer;
import ru.myitschool.work.domain.entities.Status;
public class IsEmployeeBlockUseCase {
private final AdminRepository repository;
public IsEmployeeBlockUseCase(AdminRepository repository) {
this.repository = repository;
}
public void execute(@NonNull String auth, @NonNull String login, @NonNull Consumer<Status<Boolean>> callback) {
repository.isEmployeeBlock(auth, login, callback);
}
}

View File

@ -0,0 +1,19 @@
package ru.myitschool.work.domain.admin;
import androidx.annotation.NonNull;
import java.util.function.Consumer;
import ru.myitschool.work.domain.entities.Status;
public class SetBlockConditionUseCase {
private final AdminRepository repository;
public SetBlockConditionUseCase(AdminRepository repository) {
this.repository = repository;
}
public void execute(@NonNull String auth, @NonNull String login, @NonNull Boolean condition, @NonNull Consumer<Status<Void>> callback) {
repository.setBlockCondition(auth, login, condition, callback);
}
}

View File

@ -13,8 +13,8 @@ public class AuthUseCase {
this.repository = repository;
}
public void execute(@NonNull String login, @NonNull Consumer<Status<Boolean>> callback) {
repository.auth(login, status -> {
public void execute(@NonNull String auth, @NonNull Consumer<Status<Boolean>> callback) {
repository.auth(auth, status -> {
callback.accept(new Status<>(
status.getStatusCode(),
(status.getStatusCode() == 200 || status.getStatusCode() == 401) ? status.getStatusCode() == 200 : null,

View File

@ -7,5 +7,5 @@ import java.util.function.Consumer;
import ru.myitschool.work.domain.entities.Status;
public interface LoginRepository {
void auth(@NonNull String login, @NonNull Consumer<Status<Void>> callback);
void auth(@NonNull String auth, @NonNull Consumer<Status<Void>> callback);
}

View File

@ -5,7 +5,6 @@ import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R
// НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА!
@AndroidEntryPoint
class RootActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -1,10 +1,12 @@
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.text.TextWatcher;
import android.util.Base64;
import android.view.View;
import androidx.annotation.NonNull;
@ -14,14 +16,19 @@ import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import java.nio.charset.StandardCharsets;
import ru.myitschool.work.R;
import ru.myitschool.work.databinding.FragmentLoginBinding;
import ru.myitschool.work.ui.employee.EmployeeFragment;
public class LoginFragment extends Fragment {
private static final String SHARED_PREFERENCES_LOGIN_PREFERENCES = "l01";
private static final String SHARED_PREFERENCES_LOGIN_STRING = "l02";
private String log = "", pas = "";
private FragmentLoginBinding binding;
private LoginViewModel viewModel;
@ -42,34 +49,51 @@ public class LoginFragment extends Fragment {
}
binding = FragmentLoginBinding.bind(view);
viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
binding.username.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
viewModel.onChangeLogin(s.toString());
log = s.toString();
viewModel.onChangeLoginPassword(log, pas);
binding.error.setVisibility(View.GONE);
}
});
binding.password.addTextChangedListener(new 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) {
pas = s.toString();
viewModel.onChangeLoginPassword(log, pas);
binding.error.setVisibility(View.GONE);
}
});
binding.login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModel.startQuery();
binding.username.setText("");
binding.password.setText("");
Navigation.findNavController(getView()).navigate(
R.id.action_login_fragment_to_employee_fragment
);
binding.error.setVisibility(View.GONE);
}
});
subscribe(viewModel);
viewModel.onChangeLogin(binding.username.toString());
viewModel.onChangeLoginPassword(binding.username.toString(), binding.password.toString());
}
@ -87,7 +111,7 @@ public class LoginFragment extends Fragment {
if (s.equals("Correct Login")) {
SharedPreferences sharedPreferences = getContext().getSharedPreferences(SHARED_PREFERENCES_LOGIN_PREFERENCES, Context.MODE_PRIVATE);
sharedPreferences.edit()
.putString(SHARED_PREFERENCES_LOGIN_STRING, binding.username.getText().toString())
.putString(SHARED_PREFERENCES_LOGIN_STRING, "Basic " + Base64.encodeToString((binding.username.toString() + ":" + binding.password.toString()).getBytes(StandardCharsets.UTF_8), Base64.DEFAULT))
.apply();
Navigation.findNavController(getView()).navigate(

View File

@ -1,9 +1,15 @@
package ru.myitschool.work.ui.login;
import android.util.Base64;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.navigation.Navigation;
import java.nio.charset.StandardCharsets;
import ru.myitschool.work.R;
import ru.myitschool.work.data.repository.LoginRepositoryImpl;
import ru.myitschool.work.domain.login.AuthUseCase;
@ -20,14 +26,16 @@ public class LoginViewModel extends ViewModel {
private String login;
private String password;
public void onChangeLogin(String login) {
public void onChangeLoginPassword(String login, String password) {
this.login = login;
onChangeButtonState(login);
this.password = password;
onChangeButtonState(login, password);
}
private void onChangeButtonState(String login) {
if (login.length() >= 3 && !Character.isDigit(login.charAt(0)) && login.matches("^[a-zA-Z0-9]*$")) {
private void onChangeButtonState(String login, String password) {
if (login.length() >= 3 && !Character.isDigit(login.charAt(0)) && login.matches("^[a-zA-Z0-9]*$") && password.length() >= 3) {
mutableButtonStateLiveData.postValue(true);
} else {
mutableButtonStateLiveData.postValue(false);
@ -35,8 +43,8 @@ public class LoginViewModel extends ViewModel {
}
public void startQuery() {
final String currentLogin = login;
authUseCase.execute(currentLogin, status -> {
final String currentLogin = login, currentPassword = password;
authUseCase.execute("Basic " + Base64.encodeToString((currentLogin + ":" + currentPassword).getBytes(StandardCharsets.UTF_8), Base64.DEFAULT), status -> {
if (status.getStatusCode() == 200) {
mutableLoginStateLiveData.postValue("Correct Login");
} else /*(status.getStatusCode() == 401) */ {

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1920"
android:viewportHeight="1920"
android:tint="#03A9F4"
android:alpha="0.8">
<path
android:pathData="M276.9,440.6v565.7c0,422.4 374.2,625.5 674.7,788.7l8,4.3 8.1,-4.3c300.5,-163.2 674.7,-366.3 674.7,-788.7L1642.5,440.6l-682.8,-321.7L276.9,440.6ZM959.7,1920.1c-9.3,0 -18.5,-2.4 -27,-6.9l-34.9,-19C588.1,1726.1 164,1495.9 164,1006.3L164,404.8c0,-21.9 12.6,-41.8 32.4,-51.2L935.7,5.4c15.1,-7.2 32.9,-7.2 48,0l739.3,348.2c19.8,9.4 32.4,29.3 32.4,51.2v601.5c0,489.6 -424.2,719.8 -733.8,887.9l-34.9,19c-8.5,4.5 -17.7,6.9 -27.1,6.9ZM1426.8,1372.5h-313.4l-91.6,-91.5v-83.8L905,1197.2v-116.8h-83.7l-58.5,-58.5c-1.9,0.1 -3.8,0.1 -5.8,0.1 -176.1,0 -319.3,-143.2 -319.3,-319.3 0,-176.1 143.2,-319.4 319.3,-319.4 176.1,0 319.3,143.3 319.3,319.4 0,1.9 0,3.8 -0.1,5.6l350.6,350.7v313.4ZM1160.2,1259.5h153.7v-153.7L958.5,750.2l4,-37.3c1,-123.9 -91.6,-216.6 -205.3,-216.6S550.7,589 550.7,702.7c0,113.8 92.6,206.3 206.3,206.3l47.2,-5.3 63.8,63.7h149.9v116.8h116.8v150l25.4,25.3ZM846.8,706c0,46.8 -37.9,84.7 -84.7,84.7 -46.8,0 -84.7,-37.9 -84.7,-84.7s37.9,-84.7 84.7,-84.7c46.8,0 84.7,37.9 84.7,84.7"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="273.5"
android:viewportHeight="273.5"
android:tint="#03A9F4"
android:alpha="0.8">
<path
android:pathData="M99.43,128.2v17.1c0,2.3 -1.87,4.17 -4.17,4.17h-5.25c-2.3,0 -4.17,-1.87 -4.17,-4.17v-17.1c0,-2.3 1.87,-4.17 4.17,-4.17h5.25C97.56,124.04 99.43,125.9 99.43,128.2zM273.5,109.13v55.25c0,11.79 -9.59,21.38 -21.38,21.38H21.38C9.59,185.75 0,176.16 0,164.38v-55.25C0,97.34 9.59,87.75 21.38,87.75h230.75C263.91,87.75 273.5,97.34 273.5,109.13zM68.16,156.96c0,-4.14 -3.36,-7.5 -7.5,-7.5H42.43v-32.93c0,-4.14 -3.36,-7.5 -7.5,-7.5s-7.5,3.36 -7.5,7.5v40.43c0,4.14 3.36,7.5 7.5,7.5h25.73C64.8,164.46 68.16,161.11 68.16,156.96zM114.43,128.2c0,-10.57 -8.6,-19.17 -19.17,-19.17h-5.25c-10.57,0 -19.17,8.6 -19.17,19.17v17.1c0,10.57 8.6,19.17 19.17,19.17h5.25c10.57,0 19.17,-8.6 19.17,-19.17V128.2zM165.45,145.3c0,-4.14 -3.36,-7.5 -7.5,-7.5h-6.82c-4.14,0 -7.5,3.36 -7.5,7.5c0,1.54 0.47,2.97 1.26,4.17h-3.86c-2.3,0 -4.17,-1.87 -4.17,-4.17v-17.1c0,-2.3 1.87,-4.17 4.17,-4.17h5.25c1.3,0 2.22,0.56 2.75,1.04c3.11,2.74 7.84,2.45 10.59,-0.66c2.74,-3.11 2.45,-7.84 -0.66,-10.59c-3.5,-3.09 -8.01,-4.79 -12.68,-4.79h-5.25c-10.57,0 -19.17,8.6 -19.17,19.17v17.1c0,10.57 8.6,19.17 19.17,19.17h5.25C156.85,164.46 165.45,155.87 165.45,145.3zM187.68,116.54c0,-4.14 -3.36,-7.5 -7.5,-7.5s-7.5,3.36 -7.5,7.5v40.43c0,4.14 3.36,7.5 7.5,7.5s7.5,-3.36 7.5,-7.5V116.54zM243.43,116.54c0,-4.14 -3.36,-7.5 -7.5,-7.5s-7.5,3.36 -7.5,7.5v18.6l-18.08,-23.21c-1.97,-2.53 -5.32,-3.53 -8.35,-2.48c-3.03,1.04 -5.06,3.89 -5.06,7.09v40.43c0,4.14 3.36,7.5 7.5,7.5s7.5,-3.36 7.5,-7.5v-18.6l18.08,23.21c1.45,1.86 3.65,2.89 5.92,2.89c0.81,0 1.64,-0.13 2.44,-0.41c3.03,-1.04 5.06,-3.89 5.06,-7.09V116.54z"
android:fillColor="#000000"/>
</vector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="16dp" />
<solid android:color="#03A9F4" />
</shape>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="32"
android:viewportHeight="32"
android:tint="#03A9F4"
android:alpha="0.8">
<path
android:fillColor="#FF000000"
android:pathData="M16,14c-3.86,0 -7,-3.14 -7,-7s3.14,-7 7,-7s7,3.14 7,7S19.86,14 16,14zM16,2c-2.757,0 -5,2.243 -5,5s2.243,5 5,5s5,-2.243 5,-5S18.757,2 16,2z"/>
<path
android:fillColor="#FF000000"
android:pathData="M23.942,32H8.058C5.82,32 4,30.18 4,27.942c0,-6.617 5.383,-12 12,-12s12,5.383 12,12C28,30.18 26.18,32 23.942,32zM16,17.942c-5.514,0 -10,4.486 -10,10C6,29.077 6.923,30 8.058,30h15.885C25.077,30 26,29.077 26,27.942C26,22.428 21.514,17.942 16,17.942z"/>
</vector>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_marginTop="100dp"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_gravity="center"
android:layout_marginBottom="50dp"
android:src="@drawable/admin_image"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/employee_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/white"
android:hint="Еnter the employee's username" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/view_employee_info"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Viewing employee information"
android:textColor="#FFFFFF"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/holo_red_dark"
android:textStyle="bold"
android:text="Error"
android:visibility="invisible"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="16dp"/>
</LinearLayout>

View File

@ -11,48 +11,134 @@
android:id="@+id/fullname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="fullname" />
android:layout_marginBottom="8dp"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/black"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
tools:text="Full Name" />
<ImageView
android:id="@+id/photo"
android:layout_width="200dp"
android:layout_height="200dp" />
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="16dp"
android:src="@drawable/user_image"/>
<TextView
android:id="@+id/position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="position" />
android:textSize="18sp"
tools:text="Position"
android:textColor="@color/black"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/lastEntry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="lastEntry" />
android:textSize="16sp"
tools:text="Last Entry"
android:textColor="@color/black"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/isLog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
tools:text="Is Log"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="1dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="20dp"
android:clipToPadding="true"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:paddingBottom="16dp" />
<TextView
android:id="@+id/isAdmin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
tools:text="Is Admin"
android:visibility="invisible"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="1dp"/>
<TextView
android:id="@+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:text="error" />
android:textColor="@android:color/holo_red_dark"
android:textStyle="bold"
tools:text="Error"
android:visibility="invisible"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/admin_panel"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Admin Panel"
android:layout_marginBottom="16dp"
android:visibility="invisible"
android:textColor="#FFFFFF" />
<Button
android:id="@+id/logout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="logout" />
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Logout"
android:layout_marginBottom="8dp"
android:textColor="#FFFFFF" />
<Button
android:id="@+id/refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="refresh" />
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Refresh"
android:layout_marginBottom="8dp"
android:textColor="#FFFFFF" />
<Button
android:id="@+id/scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="scan" />
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Scan"
android:layout_marginBottom="8dp"
android:textColor="#FFFFFF" />
</LinearLayout>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/employee_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/black"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:text="Full Name" />
<ImageView
android:id="@+id/employee_photo"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="16dp"
android:src="@drawable/user_image"/>
<TextView
android:id="@+id/position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="Position"
android:textColor="@color/black"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/lastEntry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="Last Entry"
android:textColor="@color/black"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/holo_red_dark"
android:textStyle="bold"
android:text="Error"
android:visibility="invisible"
android:layout_gravity="center"
android:layout_marginStart="25dp"
android:layout_marginEnd="25dp"
android:layout_marginBottom="200dp"/>
<Button
android:id="@+id/block_pass"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Block pass"
android:textColor="#FFFFFF"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/unlock_pass"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Unlock pass"
android:textColor="#FFFFFF" />
</LinearLayout>

View File

@ -5,22 +5,65 @@
android:orientation="vertical"
android:padding="20dp">
<EditText
<ImageView
android:layout_marginTop="100dp"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_gravity="center"
android:layout_marginBottom="50dp"
android:src="@drawable/login_image"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="hint" />
android:layout_gravity="center"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/white"
android:hint="Enter your username" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/white"
android:hint="Enter your password" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="login" />
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Login"
android:textColor="#FFFFFF" />
<TextView
android:id="@+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</LinearLayout>

View File

@ -9,12 +9,19 @@
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="400dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="result" />
<Button
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="close" />
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_gravity="center"
android:background="@drawable/rounded_button"
android:text="Close"
android:textColor="#FFFFFF" />
</LinearLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/nav_graph.xml"
app:startDestination="@id/login_fragment">

View File

@ -3,4 +3,6 @@
<string name="operation_was_cancelled">Operation was cancelled</string>
<string name="success">Success</string>
<string name="something_wrong">Something wrong</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>