With avatar

This commit is contained in:
2025-12-01 21:45:54 +06:00
parent 04ac941ba4
commit beb48ab41e
10 changed files with 95 additions and 75 deletions

View File

@@ -11,7 +11,6 @@ object DateUtils {
private val bookFormatter: DateTimeFormatter =
DateTimeFormatter.ofPattern("dd.MM")
/** Пытаемся распарсить дату в нескольких форматах */
fun parseDate(raw: String): LocalDate? {
return runCatching { LocalDate.parse(raw) }.getOrElse {
runCatching { OffsetDateTime.parse(raw).toLocalDate() }.getOrElse {
@@ -22,13 +21,13 @@ object DateUtils {
}
}
/** Формат для главного экрана: dd.MM.yyyy */
// Формат для главного экрана: dd.MM.yyyy
fun formatForMain(raw: String): String {
val date = parseDate(raw) ?: return raw
return date.format(mainFormatter)
}
/** Формат для экрана бронирования: dd.MM */
// Формат для экрана бронирования: dd.MM
fun formatForBook(raw: String): String {
val date = parseDate(raw) ?: return raw
return date.format(bookFormatter)

View File

@@ -13,7 +13,7 @@ object AuthRepository {
private val KEY_CODE = stringPreferencesKey("auth_code")
private var codeCache: String? = null
// Проверка кода на сервере и сохранение при успехе
suspend fun checkAndSave(text: String): Result<Boolean> {
return NetworkDataSource.checkAuth(text).onSuccess { success ->
if (success) {
@@ -25,7 +25,7 @@ object AuthRepository {
}
}
/** Сохранённый код (из кэша или DataStore) */
suspend fun getSavedCode(): String? {
codeCache?.let { return it }
@@ -37,7 +37,7 @@ object AuthRepository {
return code
}
/** Полная очистка данных авторизации (для logout) */
suspend fun clear() {
codeCache = null
dataStore.edit { prefs ->

View File

@@ -11,9 +11,7 @@ import ru.myitschool.work.domain.entities.UserEntity
object UserRepository {
/**
* Получение информации о пользователе через GET /api/<CODE>/info
*/
suspend fun getUserInfo(): Result<UserEntity> {
val code = AuthRepository.getSavedCode()
?: return Result.failure(IllegalStateException("Auth code is not saved"))
@@ -23,9 +21,7 @@ object UserRepository {
.map { dto -> dto.toDomainUser() }
}
/**
* Доступные слоты бронирования через GET /api/<CODE>/booking
*/
suspend fun getAvailableBookings(): Result<List<BookingEntity>> {
val code = AuthRepository.getSavedCode()
?: return Result.failure(IllegalStateException("Auth code is not saved"))
@@ -35,9 +31,7 @@ object UserRepository {
.map { map -> map.toDomainBookings() }
}
/**
* Создание нового бронирования через POST /api/<CODE>/book
*/
suspend fun book(date: String, placeId: Int): Result<Unit> {
val code = AuthRepository.getSavedCode()
?: return Result.failure(IllegalStateException("Auth code is not saved"))
@@ -49,7 +43,6 @@ object UserRepository {
)
}
// -------------------- Маппинг DTO -> domain --------------------
private fun UserDto.toDomainUser(): UserEntity {
val bookings = booking

View File

@@ -21,10 +21,7 @@ import ru.myitschool.work.data.source.dto.UserDto
object NetworkDataSource {
/**
* Поставь false, когда поднимешь настоящий бэкенд.
* При true используются локальные заглушки.
*/
private const val USE_STUB: Boolean = false
private val client by lazy {
@@ -73,9 +70,7 @@ object NetworkDataSource {
// ----------------- Публичные методы -----------------
/**
* Проверка кода авторизации через GET /api/<CODE>/auth
*/
suspend fun checkAuth(code: String): Result<Boolean> {
if (USE_STUB) {
// Примитивная проверка заглушки: 4+ символа → ок
@@ -95,9 +90,7 @@ object NetworkDataSource {
}
}
/**
* Получение информации о пользователе через GET /api/<CODE>/info
*/
suspend fun getUserInfo(code: String): Result<UserDto> {
if (USE_STUB) {
return Result.success(stubUser)
@@ -116,9 +109,7 @@ object NetworkDataSource {
}
}
/**
* Получение доступных слотов бронирования через GET /api/<CODE>/booking
*/
suspend fun getAvailableBookings(
code: String,
): Result<Map<String, List<AvailablePlaceDto>>> {
@@ -140,11 +131,7 @@ object NetworkDataSource {
}
}
/**
* Создание нового бронирования через POST /api/<CODE>/book
*
* Тело: { "date": "2025-01-05", "placeID": 1 }
*/
suspend fun book(
code: String,
date: String,

View File

@@ -3,19 +3,6 @@ package ru.myitschool.work.data.source.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* DTO для ответа GET /api/<CODE>/info
*
* Пример:
* {
* "name":"Иванов Петр Федорович",
* "photoUrl":"https://...",
* "booking":{
* "2025-01-05": {"id":1,"place":"102"},
* "2025-01-06": {"id":2,"place":"209.13"}
* }
* }
*/
@Serializable
data class UserDto(
@SerialName("name")
@@ -26,9 +13,7 @@ data class UserDto(
val booking: Map<String, BookedPlaceDto> = emptyMap(),
)
/**
* Элемент бронирования в ответе /info и /booking.
*/
@Serializable
data class BookedPlaceDto(
@SerialName("id")
@@ -37,13 +22,7 @@ data class BookedPlaceDto(
val place: String,
)
/**
* DTO для доступных мест в ответе GET /api/<CODE>/booking:
*
* {
* "2025-01-05": [{"id": 1, "place": "102"}, ...]
* }
*/
@Serializable
data class AvailablePlaceDto(
@SerialName("id")
@@ -52,14 +31,7 @@ data class AvailablePlaceDto(
val place: String,
)
/**
* Тело запроса POST /api/<CODE>/book:
*
* {
* "date": "2025-01-05",
* "placeID": 1
* }
*/
@Serializable
data class BookRequestDto(
@SerialName("date")

View File

@@ -2,12 +2,7 @@ package ru.myitschool.work.domain.booking
import ru.myitschool.work.data.repo.UserRepository
/**
* Юзкейс для создания бронирования.
*
* @param date дата бронирования в формате yyyy-MM-dd
* @param placeId идентификатор места (placeID из бэкенда)
*/
class BookPlaceUseCase(
private val repository: UserRepository,
) {

View File

@@ -40,7 +40,7 @@ fun AppNavHost(
}
}
// Пока не знаем стартовый экран — ничего не рисуем (можно сюда потом воткнуть Splash)
val destination = startDestination ?: return
NavHost(

View File

@@ -27,12 +27,11 @@ class AuthViewModel : ViewModel() {
private val _uiState = MutableStateFlow<AuthState>(AuthState.Data())
val uiState: StateFlow<AuthState> = _uiState.asStateFlow()
// единичное событие навигации на главный экран
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow
init {
// Если код уже сохранён — сразу уходим на главный экран
viewModelScope.launch(Dispatchers.Default) {
val saved = getSavedAuthCodeUseCase()
if (saved != null) {

View File

@@ -20,6 +20,5 @@ sealed interface BookState {
val message: String,
) : BookState
/** Нет доступных дат — показываем "Всё забронировано" */
object Empty : BookState
}