main #6
@@ -1,7 +1,7 @@
|
|||||||
package ru.myitschool.work.core
|
package ru.myitschool.work.core
|
||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
const val HOST = "http://10.0.2.2:8080"
|
const val HOST = "http://192.168.1.39:8080"
|
||||||
const val AUTH_URL = "/auth"
|
const val AUTH_URL = "/auth"
|
||||||
const val INFO_URL = "/info"
|
const val INFO_URL = "/info"
|
||||||
const val BOOKING_URL = "/booking"
|
const val BOOKING_URL = "/booking"
|
||||||
|
|||||||
11
app/src/main/java/ru/myitschool/work/data/entity/Booking.kt
Normal file
11
app/src/main/java/ru/myitschool/work/data/entity/Booking.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package ru.myitschool.work.data.entity
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
|
||||||
|
data class Booking ( val id: Long,
|
||||||
|
val date: LocalDate,
|
||||||
|
val place: Place,
|
||||||
|
val employeeCode: String){
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ru.myitschool.work.data.entity
|
||||||
|
|
||||||
|
data class Employee (
|
||||||
|
val name: String,
|
||||||
|
val code: String,
|
||||||
|
val photoUrl: String,
|
||||||
|
val bookingList: MutableList<Booking?>) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package ru.myitschool.work.data.entity
|
||||||
|
|
||||||
|
data class Place(
|
||||||
|
val id: Long,
|
||||||
|
val place: String ){}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package ru.myitschool.work.data.repo
|
||||||
|
|
||||||
|
import ru.myitschool.work.data.entity.Employee
|
||||||
|
import ru.myitschool.work.data.source.DataStoreDataSource
|
||||||
|
import ru.myitschool.work.data.source.DataStoreDataSource.createAuthCode
|
||||||
|
import ru.myitschool.work.data.source.DataStoreDataSource.getAuthCode
|
||||||
|
import ru.myitschool.work.data.source.NetworkDataSource
|
||||||
|
|
||||||
|
class MainRepository {
|
||||||
|
private var employee: Employee? = null
|
||||||
|
|
||||||
|
suspend fun getUserInfo(): Result<Employee> {
|
||||||
|
return try {
|
||||||
|
val code = getCode()
|
||||||
|
val result = NetworkDataSource.getUserInfo(code)
|
||||||
|
result.onSuccess { success ->
|
||||||
|
employee = success
|
||||||
|
}
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun getCode(): String {
|
||||||
|
return getAuthCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun logOut(){
|
||||||
|
DataStoreDataSource.logOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.Preferences
|
|||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import ru.myitschool.work.App
|
import ru.myitschool.work.App
|
||||||
import ru.myitschool.work.core.OurConstants.DS_AUTH_KEY
|
import ru.myitschool.work.core.OurConstants.DS_AUTH_KEY
|
||||||
@@ -28,7 +29,20 @@ object DataStoreDataSource {
|
|||||||
App.context.dataStore.updateData {
|
App.context.dataStore.updateData {
|
||||||
it.toMutablePreferences().also { preferences ->
|
it.toMutablePreferences().also { preferences ->
|
||||||
preferences[AUTH_KEY] = code
|
preferences[AUTH_KEY] = code
|
||||||
Log.d("AnnaKonda", "Code added to ds")
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getAuthCode(): String {
|
||||||
|
return App.context.dataStore.data.map { preferences ->
|
||||||
|
preferences[AUTH_KEY] ?: ""
|
||||||
|
}.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun logOut() {
|
||||||
|
App.context.dataStore.updateData {
|
||||||
|
it.toMutablePreferences().also { preferences ->
|
||||||
|
preferences.remove(AUTH_KEY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import ru.myitschool.work.core.Constants
|
import ru.myitschool.work.core.Constants
|
||||||
|
import ru.myitschool.work.data.entity.Employee
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import ru.myitschool.work.data.entity.Booking
|
||||||
|
import ru.myitschool.work.data.entity.Place
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
object NetworkDataSource {
|
object NetworkDataSource {
|
||||||
private val client by lazy {
|
private val client by lazy {
|
||||||
@@ -37,6 +42,69 @@ object NetworkDataSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
suspend fun getUserInfo(code: String): Result<Employee> = withContext(Dispatchers.IO) {
|
||||||
|
return@withContext runCatching {
|
||||||
|
val response = client.get(getUrl(code, Constants.INFO_URL))
|
||||||
|
|
||||||
|
when (response.status) {
|
||||||
|
HttpStatusCode.OK -> {
|
||||||
|
val json = response.bodyAsText()
|
||||||
|
if (json.isBlank()) {
|
||||||
|
error("Пустой ответ от сервера")
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsonObject = try {
|
||||||
|
Json.parseToJsonElement(json).jsonObject
|
||||||
|
} catch (e: Exception) {
|
||||||
|
error("Ошибка парсинга: ${e.message}")
|
||||||
|
}
|
||||||
|
val name = jsonObject["name"]?.jsonPrimitive?.content
|
||||||
|
?: error("Отсутствует поле 'name'")
|
||||||
|
val photoUrl = jsonObject["photoUrl"]?.jsonPrimitive?.content
|
||||||
|
?: error("Отсутствует поле 'photoUrl'")
|
||||||
|
|
||||||
|
val bookingJson = jsonObject["booking"]?.jsonObject
|
||||||
|
?: error("Отсутствует поле 'booking' в ответе")
|
||||||
|
|
||||||
|
val employee = Employee(
|
||||||
|
name = name,
|
||||||
|
code = code,
|
||||||
|
photoUrl = photoUrl,
|
||||||
|
bookingList = mutableListOf()
|
||||||
|
)
|
||||||
|
val bookingList = mutableListOf<Booking>()
|
||||||
|
for ((dateString, bookingElement) in bookingJson) {
|
||||||
|
val date = LocalDate.parse(dateString)
|
||||||
|
val bookingObj = bookingElement.jsonObject
|
||||||
|
val bookingId = bookingObj["id"]?.jsonPrimitive?.long
|
||||||
|
?: error("Отсутствует поле id")
|
||||||
|
val placeString = bookingObj["place"]?.jsonPrimitive?.content
|
||||||
|
?: error("Отсутствует поле 'place' $dateString")
|
||||||
|
|
||||||
|
if (placeString.isBlank()) {
|
||||||
|
error("Пустое поле 'place' $dateString")
|
||||||
|
}
|
||||||
|
|
||||||
|
val placeId = bookingId
|
||||||
|
val place = Place(placeId, placeString)
|
||||||
|
|
||||||
|
val booking = Booking(
|
||||||
|
id = bookingId,
|
||||||
|
date = date,
|
||||||
|
place = place,
|
||||||
|
employeeCode = employee.code
|
||||||
|
)
|
||||||
|
bookingList.add(booking)
|
||||||
|
}
|
||||||
|
if (bookingList.isEmpty()) {
|
||||||
|
error("Список бронирований пуст")
|
||||||
|
}
|
||||||
|
employee.bookingList.addAll(bookingList)
|
||||||
|
employee
|
||||||
|
}
|
||||||
|
else -> error(response.bodyAsText())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
|
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package ru.myitschool.work.domain.main
|
||||||
|
|
||||||
|
import ru.myitschool.work.data.entity.Employee
|
||||||
|
import ru.myitschool.work.data.repo.AuthRepository
|
||||||
|
import ru.myitschool.work.data.repo.MainRepository
|
||||||
|
|
||||||
|
class GetUserDataUseCase(
|
||||||
|
private val repository: MainRepository
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(): Result<Employee> {
|
||||||
|
return repository.getUserInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,12 +33,7 @@ fun AppNavHost(
|
|||||||
AuthScreen(navController = navController)
|
AuthScreen(navController = navController)
|
||||||
}
|
}
|
||||||
composable<MainScreenDestination> {
|
composable<MainScreenDestination> {
|
||||||
MainScreen(
|
MainScreen(navController = navController)
|
||||||
navController = navController,
|
|
||||||
onNavigateToBooking = {
|
|
||||||
navController.navigate(BookScreenDestination)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
composable<BookScreenDestination> {
|
composable<BookScreenDestination> {
|
||||||
BookScreen(
|
BookScreen(
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class AuthViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is AuthIntent.TextInput -> {
|
is AuthIntent.TextInput -> {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
authFlow().collect {
|
authFlow().collect {
|
||||||
if (CheckCodeInput(intent.text)) {
|
if (CheckCodeInput(intent.text)) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookingScreen(
|
fun BookingScreen(
|
||||||
uiState: BookingUiState, // состояние интерфейса
|
uiState: BookingState, // состояние интерфейса
|
||||||
onSelectDate: (LocalDate) -> Unit, // callback при выборе даты
|
onSelectDate: (LocalDate) -> Unit, // callback при выборе даты
|
||||||
onSelectPlace: (String) -> Unit, // callback при выборе места
|
onSelectPlace: (String) -> Unit, // callback при выборе места
|
||||||
onBook: () -> Unit, // callback при бронировании
|
onBook: () -> Unit, // callback при бронировании
|
||||||
@@ -136,22 +136,15 @@ fun BookingScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Модель состояния интерфейса
|
|
||||||
data class BookingUiState(
|
|
||||||
val dates: List<LocalDate> = emptyList(), // список доступных дат
|
|
||||||
val places: Map<LocalDate, List<String>> = emptyMap(), // места по датам
|
|
||||||
val selectedDate: LocalDate? = null, // выбранная дата
|
|
||||||
val selectedPlace: String? = null, // выбранное место
|
|
||||||
val isError: Boolean = false, // флаг ошибки
|
|
||||||
val errorMessage: String? = null // сообщение об ошибке
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookScreen(
|
fun BookScreen(
|
||||||
onBack: () -> Unit, // callback при возврате назад
|
onBack: () -> Unit, // callback при возврате назад
|
||||||
onBookingSuccess: () -> Unit // callback при успешном бронировании
|
onBookingSuccess: () -> Unit // callback при успешном бронировании
|
||||||
) {
|
) {
|
||||||
val viewModel: BookingViewModel = viewModel()
|
val viewModel: BookingViewModel = BookingViewModel()
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
BookingScreen(
|
BookingScreen(
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.book
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
data class BookingState(
|
||||||
|
val dates: List<LocalDate> = emptyList(), // список доступных дат
|
||||||
|
val places: Map<LocalDate, List<String>> = emptyMap(), // места по датам
|
||||||
|
val selectedDate: LocalDate? = null, // выбранная дата
|
||||||
|
val selectedPlace: String? = null, // выбранное место
|
||||||
|
val isError: Boolean = false, // флаг ошибки
|
||||||
|
val errorMessage: String? = null // сообщение об ошибке
|
||||||
|
)
|
||||||
@@ -10,8 +10,8 @@ import java.time.LocalDate
|
|||||||
|
|
||||||
class BookingViewModel : ViewModel() {
|
class BookingViewModel : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(BookingUiState())
|
private val _uiState = MutableStateFlow(BookingState())
|
||||||
val uiState: StateFlow<BookingUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<BookingState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadBookingData()
|
loadBookingData()
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.main
|
||||||
|
|
||||||
|
import ru.myitschool.work.ui.screen.auth.AuthAction
|
||||||
|
|
||||||
|
sealed interface MainAction {
|
||||||
|
data class SetName(val name: String)
|
||||||
|
data class ShowError(val message: String?) : MainAction
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.main
|
||||||
|
|
||||||
|
sealed interface MainIntent {
|
||||||
|
/* data class Send(val text: String): AuthIntent
|
||||||
|
data class TextInput(val text: String): AuthIntent
|
||||||
|
object CheckLogIntent: AuthIntent*/
|
||||||
|
object LoadData: MainIntent
|
||||||
|
object LogOut: MainIntent
|
||||||
|
}
|
||||||
@@ -1,100 +1,81 @@
|
|||||||
package ru.myitschool.work.ui.screen.main
|
package ru.myitschool.work.ui.screen.main
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import kotlinx.coroutines.launch
|
import coil3.compose.AsyncImage
|
||||||
import ru.myitschool.work.core.TestIds
|
import ru.myitschool.work.core.TestIds
|
||||||
import java.text.SimpleDateFormat
|
import ru.myitschool.work.data.entity.Booking
|
||||||
import java.util.*
|
import ru.myitschool.work.data.entity.Employee
|
||||||
|
import ru.myitschool.work.ui.nav.AuthScreenDestination
|
||||||
import ru.myitschool.work.ui.nav.BookScreenDestination
|
import ru.myitschool.work.ui.nav.BookScreenDestination
|
||||||
|
|
||||||
// Модель данных для бронирования
|
|
||||||
data class BookingItem(
|
|
||||||
val date: String, // Формат "dd.MM.yyyy"
|
|
||||||
val place: String,
|
|
||||||
val id: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
onNavigateToBooking: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
|
val viewModel = MainViewModel()
|
||||||
// Состояния
|
// Состояния
|
||||||
var userName by remember { mutableStateOf("Иван Иванов") }
|
val event = viewModel.actionFlow.collectAsState(initial = null)
|
||||||
var isLoading by remember { mutableStateOf(false) }
|
|
||||||
var errorMessage by remember { mutableStateOf("") }
|
|
||||||
var bookingItems by remember { mutableStateOf(emptyList<BookingItem>()) }
|
|
||||||
var hasError by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
// Для корутин
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
// Функция загрузки данных
|
// Функция загрузки данных
|
||||||
fun loadData() {
|
|
||||||
isLoading = true
|
|
||||||
hasError = false
|
|
||||||
|
|
||||||
coroutineScope.launch {
|
|
||||||
kotlinx.coroutines.delay(1000) // Имитация задержки
|
|
||||||
|
|
||||||
// Имитация ответа от сервера
|
|
||||||
val response = listOf(
|
|
||||||
BookingItem("20.12.2023", "Конференц-зал А", 1),
|
|
||||||
BookingItem("15.12.2023", "Переговорная Б", 2),
|
|
||||||
BookingItem("25.12.2023", "Спортзал", 3)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Сортировка по дате (увеличение)
|
|
||||||
bookingItems = response.sortedBy {
|
|
||||||
SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()).parse(it.date)
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Первая загрузка при открытии экрана
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
loadData()
|
viewModel.onIntent(MainIntent.LoadData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errorMessage: String? by remember { mutableStateOf("") }
|
||||||
|
LaunchedEffect(event.value) {
|
||||||
|
if (event.value is MainAction.ShowError) {
|
||||||
|
errorMessage = (event.value as MainAction.ShowError).message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d("AnnaKonda", errorMessage.toString())
|
||||||
// Если ошибка - показываем только ошибку и кнопку обновления
|
// Если ошибка - показываем только ошибку и кнопку обновления
|
||||||
if (hasError) {
|
if (errorMessage != null) {
|
||||||
Column(
|
ErrorScreen(viewModel = viewModel, navController = navController, errorMessage)
|
||||||
modifier = Modifier.fillMaxSize(),
|
} else {
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
DefaultScreen(viewModel = viewModel, navController = navController)
|
||||||
verticalArrangement = Arrangement.Center
|
}
|
||||||
) {
|
}
|
||||||
// Текстовое поле с ошибкой (main_error)
|
@Composable
|
||||||
Text(
|
fun DefaultScreen(viewModel: MainViewModel,
|
||||||
text = errorMessage,
|
navController: NavController){
|
||||||
color = MaterialTheme.colorScheme.error,
|
val state by viewModel.uiState.collectAsState()
|
||||||
modifier = Modifier.testTag(TestIds.Main.ERROR)
|
var employee : Employee? by remember { mutableStateOf(null) }
|
||||||
|
var errorMessage by remember { mutableStateOf("") }
|
||||||
|
var bookingItems : List<Booking?>? by remember { mutableStateOf(emptyList<Booking>()) }
|
||||||
|
var isLoading by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
)
|
LaunchedEffect(state) {
|
||||||
|
when (state) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
is MainState.Loading -> {
|
||||||
|
errorMessage = ""
|
||||||
// Кнопка обновления (main_refresh_button)
|
isLoading = true
|
||||||
Button(onClick = { loadData() }) {
|
}
|
||||||
Text("Обновить")
|
is MainState.Data -> {
|
||||||
|
isLoading = false
|
||||||
|
employee = (state as MainState.Data).employee
|
||||||
|
if (employee == null){
|
||||||
|
navController.navigate(AuthScreenDestination) { popUpTo(0) }
|
||||||
|
} else {
|
||||||
|
bookingItems = employee?.bookingList?.sortedBy { item ->
|
||||||
|
item?.date
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Нормальное состояние
|
employee?.let {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -106,29 +87,34 @@ fun MainScreen(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Фото пользователя (main_photo)
|
// Фото пользователя (main_photo)
|
||||||
Image(
|
employee?.photoUrl?.let { msg -> Log.d("AnnaKonda", msg) }
|
||||||
painter = painterResource(id = android.R.drawable.ic_menu_gallery),
|
AsyncImage(
|
||||||
|
model = employee?.photoUrl ?: "",
|
||||||
contentDescription = "Фото",
|
contentDescription = "Фото",
|
||||||
modifier = Modifier.size(64.dp).testTag(TestIds.Main.PROFILE_IMAGE)
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.testTag(TestIds.Main.PROFILE_IMAGE),
|
||||||
|
error = painterResource(id = android.R.drawable.ic_menu_gallery)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
|
||||||
// Имя пользователя (main_name)
|
// Имя пользователя (main_name)
|
||||||
Text(
|
Text(
|
||||||
text = userName,
|
text = employee!!.name,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
modifier = Modifier.weight(1f).testTag(TestIds.Main.PROFILE_NAME),
|
modifier = Modifier.weight(1f).testTag(TestIds.Main.PROFILE_NAME),
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
|
|
||||||
// Кнопка выхода (main_logout_button)
|
// Кнопка выхода (main_logout_button)
|
||||||
Button(onClick = {
|
Button(
|
||||||
// Очистка данных и переход на авторизацию
|
onClick = {
|
||||||
userName = ""
|
// Очистка данных и переход на авторизацию
|
||||||
bookingItems = emptyList()
|
viewModel.onIntent(MainIntent.LogOut)
|
||||||
navController.navigate("auth") { popUpTo(0) }
|
bookingItems = emptyList()
|
||||||
},
|
},
|
||||||
modifier = Modifier.testTag(TestIds.Main.LOGOUT_BUTTON)
|
modifier = Modifier.testTag(TestIds.Main.LOGOUT_BUTTON)
|
||||||
) {
|
) {
|
||||||
Text("Выход")
|
Text("Выход")
|
||||||
@@ -145,11 +131,11 @@ fun MainScreen(
|
|||||||
) {
|
) {
|
||||||
// Кнопка обновления (main_refresh_button)
|
// Кнопка обновления (main_refresh_button)
|
||||||
Button(
|
Button(
|
||||||
onClick = { loadData() },
|
onClick = { viewModel.onIntent(MainIntent.LoadData) },
|
||||||
enabled = !isLoading,
|
enabled = state !is MainState.Loading,
|
||||||
modifier = Modifier.testTag(TestIds.Main.REFRESH_BUTTON)
|
modifier = Modifier.testTag(TestIds.Main.REFRESH_BUTTON)
|
||||||
) {
|
) {
|
||||||
if (isLoading) {
|
if (state is MainState.Loading) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
color = MaterialTheme.colorScheme.onPrimary
|
color = MaterialTheme.colorScheme.onPrimary
|
||||||
@@ -160,7 +146,8 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
// кнопка бронирования
|
// кнопка бронирования
|
||||||
Button(
|
Button(
|
||||||
onClick = { navController.navigate(BookScreenDestination)
|
onClick = {
|
||||||
|
navController.navigate(BookScreenDestination)
|
||||||
},
|
},
|
||||||
modifier = Modifier.testTag(TestIds.Main.ADD_BUTTON)
|
modifier = Modifier.testTag(TestIds.Main.ADD_BUTTON)
|
||||||
) {
|
) {
|
||||||
@@ -171,14 +158,13 @@ fun MainScreen(
|
|||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
// Список бронирований
|
// Список бронирований
|
||||||
if (bookingItems.isNotEmpty()) {
|
if (!bookingItems.isNullOrEmpty()) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
|
|
||||||
) {
|
) {
|
||||||
itemsIndexed(bookingItems) { index, item ->
|
itemsIndexed(bookingItems as List<Booking?>) { index, item ->
|
||||||
// Элемент списка (main_book_pos_{index})
|
// Элемент списка (main_book_pos_{index})
|
||||||
Log.d("Nicoly", index.toString())
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -187,10 +173,13 @@ fun MainScreen(
|
|||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp).testTag(TestIds.Main.getIdItemByPosition(index))) {
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
.testTag(TestIds.Main.getIdItemByPosition(index))
|
||||||
|
) {
|
||||||
// Дата бронирования (main_item_date)
|
// Дата бронирования (main_item_date)
|
||||||
Text(
|
Text(
|
||||||
text = "Дата: ${item.date}",
|
text = "Дата: ${item?.date}",
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.testTag(TestIds.Main.ITEM_DATE)
|
modifier = Modifier.testTag(TestIds.Main.ITEM_DATE)
|
||||||
)
|
)
|
||||||
@@ -199,7 +188,7 @@ fun MainScreen(
|
|||||||
|
|
||||||
// Место бронирования (main_item_place)
|
// Место бронирования (main_item_place)
|
||||||
Text(
|
Text(
|
||||||
text = "Место: ${item.place}",
|
text = "Место: ${item?.place?.place}",
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.testTag(TestIds.Main.ITEM_PLACE)
|
modifier = Modifier.testTag(TestIds.Main.ITEM_PLACE)
|
||||||
)
|
)
|
||||||
@@ -223,3 +212,34 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ErrorScreen(viewModel: MainViewModel,
|
||||||
|
navController: NavController,
|
||||||
|
errorMessage: String?){
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
// Текстовое поле с ошибкой (main_error)
|
||||||
|
|
||||||
|
if (errorMessage != null) {
|
||||||
|
Text(
|
||||||
|
text = errorMessage,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.testTag(TestIds.Main.ERROR)
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Button(onClick = { viewModel.onIntent(MainIntent.LoadData) }) {
|
||||||
|
Text("Обновить")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.main
|
||||||
|
|
||||||
|
import ru.myitschool.work.data.entity.Employee
|
||||||
|
import ru.myitschool.work.ui.screen.auth.AuthState
|
||||||
|
|
||||||
|
sealed interface MainState {
|
||||||
|
object Loading: MainState
|
||||||
|
data class Data (val employee: Employee?): MainState
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package ru.myitschool.work.ui.screen.main
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
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.MainRepository
|
||||||
|
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
|
||||||
|
import ru.myitschool.work.domain.main.GetUserDataUseCase
|
||||||
|
import ru.myitschool.work.ui.screen.auth.AuthAction
|
||||||
|
import ru.myitschool.work.ui.screen.auth.AuthIntent
|
||||||
|
import ru.myitschool.work.ui.screen.auth.AuthState
|
||||||
|
|
||||||
|
class MainViewModel : ViewModel() {
|
||||||
|
init {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
private val repository by lazy{ MainRepository() }
|
||||||
|
private val getUserDataUseCase by lazy { GetUserDataUseCase(repository) }
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow<MainState>(MainState.Loading)
|
||||||
|
val uiState: StateFlow<MainState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _actionFlow: MutableSharedFlow<MainAction> = MutableSharedFlow()
|
||||||
|
val actionFlow: SharedFlow<MainAction> = _actionFlow
|
||||||
|
|
||||||
|
fun onIntent(intent: MainIntent) {
|
||||||
|
when (intent) {
|
||||||
|
is MainIntent.LoadData -> {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
is MainIntent.LogOut -> {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
_uiState.update { MainState.Data(null) }
|
||||||
|
repository.logOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadData() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
_uiState.update { MainState.Loading }
|
||||||
|
|
||||||
|
getUserDataUseCase.invoke().fold(
|
||||||
|
onSuccess = { employee ->
|
||||||
|
_uiState.update { MainState.Data(employee) }
|
||||||
|
_actionFlow.emit(MainAction.ShowError(null))
|
||||||
|
},
|
||||||
|
onFailure = { error ->
|
||||||
|
error.printStackTrace()
|
||||||
|
if (error.message != null) {
|
||||||
|
_actionFlow.emit(MainAction.ShowError(error.message.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user