Merge pull request 'room booking' (#10) from room_board into main
Reviewed-on: Minipigi-org/NTO-2026-Android-TeamTask-Template#10
This commit is contained in:
commit
6fecb0890b
@ -1,10 +1,11 @@
|
||||
package ru.myitschool.work.core
|
||||
|
||||
object Constants {
|
||||
const val HOST = "http://10.0.0.103:49165"
|
||||
const val HOST = "http://10.0.0.12:49165"
|
||||
const val AUTH_URL = "/login"
|
||||
const val INFO_URL = "/info"
|
||||
const val BOOKING_URL = "/booking"
|
||||
const val BOOK_URL = "/book"
|
||||
const val ROOM_BOOKING_URL = "/room/booking"
|
||||
const val AUTH_DELAY = 60
|
||||
}
|
||||
@ -17,4 +17,6 @@ data class AuthResponseDto(
|
||||
val token: String,
|
||||
@SerialName("expired")
|
||||
val expired: Int,
|
||||
@SerialName("role")
|
||||
val role: String
|
||||
)
|
||||
@ -8,5 +8,5 @@ data class BookRequestDto(
|
||||
@SerialName("date")
|
||||
val date: String,
|
||||
@SerialName("placeId")
|
||||
val placeId: String,
|
||||
val placeId: Int,
|
||||
)
|
||||
@ -0,0 +1,12 @@
|
||||
package ru.myitschool.work.data.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RoomBookingRequestDto(
|
||||
@SerialName("roomId")
|
||||
val roomId: Int,
|
||||
@SerialName("date")
|
||||
val date: String,
|
||||
)
|
||||
@ -16,8 +16,12 @@ import ru.myitschool.work.data.source.NetworkDataSource
|
||||
object AuthRepository {
|
||||
private const val STORE = "AUTH-STORE"
|
||||
private const val TOKEN_KEY = "TOKEN"
|
||||
private const val ROLE_KEY = "ROLE"
|
||||
|
||||
|
||||
private var tokenCache: String? = null
|
||||
private var roleCache: String? = null
|
||||
|
||||
|
||||
suspend fun checkAndSave(login: String, password: String): Result<AuthResponseDto> {
|
||||
val data = AuthRequestDto(login=login, password=password)
|
||||
@ -29,6 +33,10 @@ object AuthRepository {
|
||||
val prefKey = stringPreferencesKey(TOKEN_KEY)
|
||||
preferences[prefKey] = encryptedTokenCache
|
||||
}
|
||||
App.context.userDataStore.edit { preferences ->
|
||||
val prefKey = stringPreferencesKey(ROLE_KEY)
|
||||
preferences[prefKey] = success.role
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,12 +55,27 @@ object AuthRepository {
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun getRole(): String? {
|
||||
if (roleCache == null) {
|
||||
roleCache = App.context.userDataStore.data
|
||||
.firstOrNull()
|
||||
?.let { preferences ->
|
||||
preferences[stringPreferencesKey(ROLE_KEY)]
|
||||
}
|
||||
}
|
||||
return roleCache
|
||||
}
|
||||
|
||||
suspend fun logout() {
|
||||
tokenCache = null
|
||||
App.context.userDataStore.edit { preferences ->
|
||||
val prefKey = stringPreferencesKey(TOKEN_KEY)
|
||||
preferences.remove(prefKey)
|
||||
}
|
||||
App.context.userDataStore.edit { preferences ->
|
||||
val prefKey = stringPreferencesKey(ROLE_KEY)
|
||||
preferences.remove(prefKey)
|
||||
}
|
||||
}
|
||||
|
||||
private val Context.userDataStore: DataStore<Preferences> by preferencesDataStore(name = STORE)
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package ru.myitschool.work.data.repo
|
||||
|
||||
import ru.myitschool.work.data.dto.BookRequestDto
|
||||
import ru.myitschool.work.data.dto.RoomBookingRequestDto
|
||||
import ru.myitschool.work.data.source.NetworkDataSource
|
||||
import ru.myitschool.work.domain.room.entities.RoomEntity
|
||||
|
||||
class RoomBookingRepository(
|
||||
private val authRepository: AuthRepository
|
||||
) {
|
||||
suspend fun getRoomBookings(): Result<RoomEntity> {
|
||||
val token = authRepository.getToken() ?: return getNoAuthResult()
|
||||
return NetworkDataSource.getRoomBookings(token).mapCatching { dto ->
|
||||
RoomEntity(dto)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteRoomBooking(roomId: Int, date: String): Result<Boolean> {
|
||||
val token = authRepository.getToken() ?: return getNoAuthResult()
|
||||
val data = RoomBookingRequestDto(roomId = roomId, date = date)
|
||||
return NetworkDataSource.deleteRoomBooking(token, data)
|
||||
}
|
||||
|
||||
suspend fun sendRoomBooking(roomId: Int, date: String): Result<Boolean> {
|
||||
val token = authRepository.getToken() ?: return getNoAuthResult()
|
||||
val dto = BookRequestDto(date, roomId)
|
||||
return NetworkDataSource.addBook(token, dto)
|
||||
}
|
||||
private fun <T> getNoAuthResult() = Result.failure<T>(
|
||||
IllegalStateException("No auth")
|
||||
)
|
||||
}
|
||||
@ -4,6 +4,7 @@ import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.request.post
|
||||
@ -21,6 +22,7 @@ import ru.myitschool.work.data.dto.AuthRequestDto
|
||||
import ru.myitschool.work.data.dto.AuthResponseDto
|
||||
import ru.myitschool.work.data.dto.PlaceDto
|
||||
import ru.myitschool.work.data.dto.BookRequestDto
|
||||
import ru.myitschool.work.data.dto.RoomBookingRequestDto
|
||||
import ru.myitschool.work.data.dto.UserDto
|
||||
import ru.myitschool.work.data.repo.AuthRepository
|
||||
|
||||
@ -114,5 +116,42 @@ object NetworkDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getRoomBookings(token: String): Result<Map<String, String?>> = withContext(Dispatchers.IO) {
|
||||
return@withContext runCatching {
|
||||
val response = client.get(getUrl(Constants.ROOM_BOOKING_URL)) {
|
||||
headers {
|
||||
append("Authorization", "Bearer $token")
|
||||
}
|
||||
}
|
||||
if (response.status == HttpStatusCode.OK) {
|
||||
response.body<Map<String, String?>>()
|
||||
} else {
|
||||
if (response.status == HttpStatusCode.Unauthorized) {
|
||||
AuthRepository.logout()
|
||||
}
|
||||
error(response.bodyAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteRoomBooking(token: String, data: RoomBookingRequestDto): Result<Boolean> = withContext(Dispatchers.IO) {
|
||||
return@withContext runCatching {
|
||||
val response = client.delete(getUrl(Constants.ROOM_BOOKING_URL)) {
|
||||
headers {
|
||||
append("Authorization", "Bearer $token")
|
||||
}
|
||||
setBody(data)
|
||||
}
|
||||
if (response.status == HttpStatusCode.OK) {
|
||||
true
|
||||
} else {
|
||||
if (response.status == HttpStatusCode.Unauthorized) {
|
||||
AuthRepository.logout()
|
||||
}
|
||||
error(response.bodyAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUrl(targetUrl: String) = "${Constants.HOST}/api$targetUrl"
|
||||
}
|
||||
@ -3,7 +3,7 @@ package ru.myitschool.work.domain.auth
|
||||
import ru.myitschool.work.data.dto.AuthResponseDto
|
||||
import ru.myitschool.work.data.repo.AuthRepository
|
||||
|
||||
class CheckAndSaveAuthCodeUseCase(
|
||||
class CheckAndSaveAuthTokenUseCase(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
@ -1,6 +1,6 @@
|
||||
package ru.myitschool.work.domain.auth
|
||||
|
||||
class CheckCodeFormatUseCase {
|
||||
class CheckCredsFormatUseCase {
|
||||
operator fun invoke(
|
||||
login: String, password: String
|
||||
): Boolean {
|
||||
@ -2,7 +2,7 @@ package ru.myitschool.work.domain.auth
|
||||
|
||||
import ru.myitschool.work.data.repo.AuthRepository
|
||||
|
||||
class GetCodeUseCase(
|
||||
class GetTokenUseCase(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke(): String? {
|
||||
@ -2,5 +2,5 @@ package ru.myitschool.work.domain.book.entities
|
||||
|
||||
data class BookRequestData(
|
||||
val date: String,
|
||||
val placeId: String
|
||||
val placeId: Int
|
||||
)
|
||||
@ -0,0 +1,11 @@
|
||||
package ru.myitschool.work.domain.room
|
||||
|
||||
import ru.myitschool.work.data.repo.RoomBookingRepository
|
||||
|
||||
class DeleteRoomBookingsUseCase(
|
||||
private val repository: RoomBookingRepository
|
||||
) {
|
||||
suspend operator fun invoke(roomId: Int, date: String): Result<Boolean> {
|
||||
return repository.deleteRoomBooking(roomId = roomId, date = date)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package ru.myitschool.work.domain.room
|
||||
|
||||
import ru.myitschool.work.data.repo.AuthRepository
|
||||
|
||||
class GetRoleUseCase(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke(): String? {
|
||||
return repository.getRole()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package ru.myitschool.work.domain.room
|
||||
|
||||
import ru.myitschool.work.data.repo.RoomBookingRepository
|
||||
import ru.myitschool.work.domain.room.entities.RoomEntity
|
||||
|
||||
class GetRoomBookingsUseCase(
|
||||
private val repository: RoomBookingRepository
|
||||
) {
|
||||
suspend operator fun invoke(): Result<RoomEntity> {
|
||||
return repository.getRoomBookings()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package ru.myitschool.work.domain.room
|
||||
|
||||
import ru.myitschool.work.data.repo.RoomBookingRepository
|
||||
|
||||
class SendRoomBookingRequestUseCase(
|
||||
private val repository: RoomBookingRepository
|
||||
) {
|
||||
suspend operator fun invoke(roomId: Int, date: String): Result<Unit> {
|
||||
return repository.sendRoomBooking(roomId, date).mapCatching { success ->
|
||||
if (!success) error("Book error")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package ru.myitschool.work.ui.nav
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data object RoomScreenDestination: AppDestination
|
||||
@ -14,14 +14,17 @@ import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import ru.myitschool.work.data.repo.AuthRepository
|
||||
import ru.myitschool.work.domain.auth.GetCodeUseCase
|
||||
import ru.myitschool.work.domain.auth.GetTokenUseCase
|
||||
import ru.myitschool.work.domain.room.GetRoleUseCase
|
||||
import ru.myitschool.work.ui.nav.AppDestination
|
||||
import ru.myitschool.work.ui.nav.AuthScreenDestination
|
||||
import ru.myitschool.work.ui.nav.BookScreenDestination
|
||||
import ru.myitschool.work.ui.nav.MainScreenDestination
|
||||
import ru.myitschool.work.ui.nav.RoomScreenDestination
|
||||
import ru.myitschool.work.ui.screen.auth.AuthScreen
|
||||
import ru.myitschool.work.ui.screen.book.BookScreen
|
||||
import ru.myitschool.work.ui.screen.main.MainScreen
|
||||
import ru.myitschool.work.ui.screen.room.RoomScreen
|
||||
|
||||
@Composable
|
||||
fun AppNavHost(
|
||||
@ -30,9 +33,12 @@ fun AppNavHost(
|
||||
) {
|
||||
var destination by remember { mutableStateOf<AppDestination?>(null) }
|
||||
LaunchedEffect(Unit) {
|
||||
val code = GetCodeUseCase(AuthRepository).invoke()
|
||||
val code = GetTokenUseCase(AuthRepository).invoke()
|
||||
val role = GetRoleUseCase(AuthRepository).invoke()
|
||||
destination = if (code == null) {
|
||||
AuthScreenDestination
|
||||
} else if (role == "room") {
|
||||
RoomScreenDestination
|
||||
} else {
|
||||
MainScreenDestination
|
||||
}
|
||||
@ -54,6 +60,9 @@ fun AppNavHost(
|
||||
composable<BookScreenDestination> {
|
||||
BookScreen(navController = navController)
|
||||
}
|
||||
composable<RoomScreenDestination> {
|
||||
RoomScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,13 +12,13 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.myitschool.work.core.Constants
|
||||
import ru.myitschool.work.data.repo.AuthRepository
|
||||
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
|
||||
import ru.myitschool.work.domain.auth.CheckCodeFormatUseCase
|
||||
import ru.myitschool.work.domain.auth.CheckAndSaveAuthTokenUseCase
|
||||
import ru.myitschool.work.domain.auth.CheckCredsFormatUseCase
|
||||
import ru.myitschool.work.ui.nav.MainScreenDestination
|
||||
|
||||
class AuthViewModel : ViewModel() {
|
||||
private val checkCodeFormatUseCase by lazy { CheckCodeFormatUseCase() }
|
||||
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) }
|
||||
private val checkCodeFormatUseCase by lazy { CheckCredsFormatUseCase() }
|
||||
private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthTokenUseCase(AuthRepository) }
|
||||
private val _uiState = MutableStateFlow<AuthState>(
|
||||
AuthState.Data(
|
||||
isEnabledSend = false,
|
||||
|
||||
@ -4,6 +4,6 @@ sealed interface BookIntent {
|
||||
data object Refresh: BookIntent
|
||||
data class Add(
|
||||
val date: String,
|
||||
val placeId: String
|
||||
val placeId: Int
|
||||
): BookIntent
|
||||
}
|
||||
@ -170,7 +170,7 @@ private fun ContentState(
|
||||
mutableIntStateOf(startDestination.index)
|
||||
}
|
||||
var selectedPlaceId by rememberSaveable {
|
||||
mutableStateOf<String?>(null)
|
||||
mutableStateOf<Int?>(null)
|
||||
}
|
||||
Box {
|
||||
Column {
|
||||
@ -213,7 +213,7 @@ private fun ContentState(
|
||||
startDestination = startDestination,
|
||||
state = state,
|
||||
onPlaceSelected = { id ->
|
||||
selectedPlaceId = id
|
||||
selectedPlaceId = id.toInt()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package ru.myitschool.work.ui.screen.room
|
||||
|
||||
sealed interface RoomIntent {
|
||||
data object Refresh: RoomIntent
|
||||
data class Booking(val placeId: Int): RoomIntent
|
||||
data object Book: RoomIntent
|
||||
data object UnBook: RoomIntent
|
||||
data class Booking(val placeId: Int, val date: String): RoomIntent
|
||||
data class UnBook(val placeId: Int, val date: String): RoomIntent
|
||||
}
|
||||
@ -1,7 +1,107 @@
|
||||
package ru.myitschool.work.ui.screen.room
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.myitschool.work.data.repo.AuthRepository
|
||||
import ru.myitschool.work.data.repo.RoomBookingRepository
|
||||
import ru.myitschool.work.domain.room.DeleteRoomBookingsUseCase
|
||||
import ru.myitschool.work.domain.room.GetRoomBookingsUseCase
|
||||
import ru.myitschool.work.domain.room.SendRoomBookingRequestUseCase
|
||||
|
||||
class RoomViewModel : ViewModel() {
|
||||
|
||||
private val roomBookingRepository = RoomBookingRepository(
|
||||
AuthRepository
|
||||
)
|
||||
private val getRoomBookingsDataUseCase by lazy {
|
||||
GetRoomBookingsUseCase(
|
||||
RoomBookingRepository(
|
||||
AuthRepository
|
||||
)
|
||||
)
|
||||
}
|
||||
private val deleteRoomBookingRequestUseCase by lazy {
|
||||
DeleteRoomBookingsUseCase(
|
||||
roomBookingRepository
|
||||
)
|
||||
}
|
||||
|
||||
private val sendRoomBookingRequestUseCase by lazy {
|
||||
SendRoomBookingRequestUseCase(
|
||||
roomBookingRepository
|
||||
)
|
||||
}
|
||||
private val _uiState = MutableStateFlow<RoomState>(RoomState.Loading)
|
||||
val uiState: StateFlow<RoomState> = _uiState.asStateFlow()
|
||||
|
||||
init {
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun onIntent(intent: RoomIntent) {
|
||||
when (intent) {
|
||||
is RoomIntent.Refresh -> {
|
||||
refresh()
|
||||
}
|
||||
|
||||
is RoomIntent.Booking -> {
|
||||
viewModelScope.launch {
|
||||
sendRoomBookingRequestUseCase.invoke(
|
||||
intent.placeId,
|
||||
intent.date
|
||||
).fold(
|
||||
onSuccess = {
|
||||
// _actionFlow.emit(BookAction.BackWithSuccess)
|
||||
refresh()
|
||||
},
|
||||
onFailure = { error ->
|
||||
error.printStackTrace()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is RoomIntent.UnBook -> {
|
||||
viewModelScope.launch {
|
||||
deleteRoomBookingRequestUseCase.invoke(
|
||||
intent.placeId,
|
||||
intent.date
|
||||
).fold(
|
||||
onSuccess = {
|
||||
// _actionFlow.emit(BookAction.BackWithSuccess)
|
||||
refresh()
|
||||
},
|
||||
onFailure = { error ->
|
||||
error.printStackTrace()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
viewModelScope.launch {
|
||||
_uiState.update { RoomState.Loading }
|
||||
_uiState.update {
|
||||
getRoomBookingsDataUseCase.invoke().fold(
|
||||
onSuccess = { data ->
|
||||
RoomState.Data(
|
||||
data = data
|
||||
)
|
||||
},
|
||||
onFailure = { error ->
|
||||
RoomState.Error(
|
||||
error = error.message.orEmpty()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user