diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5814115..118819d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -67,7 +67,8 @@ dependencies { implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material3:material3:1.1.2") + implementation("androidx.compose.material:material:1.4.3") // Room val room_version = "2.5.2" diff --git a/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt b/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt index 4253f75..e2733d7 100644 --- a/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt +++ b/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt @@ -60,6 +60,22 @@ interface MyServerService { @Body credentials: Credentials ): Token + @POST("api/performances") + suspend fun createPerformance( + @Body performance: PerformanceRemote, + ) + + @PUT("api/performances/{id}") + suspend fun updatePerformance( + @Path("id") id: Int, + @Body student: PerformanceRemote, + ) + + @DELETE("api/performances/{id}") + suspend fun deletePerformance( + @Path("id") id: Int, + ) + companion object { private const val BASE_URL = "http://10.0.2.2:8000/" diff --git a/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt b/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt index e7c2abd..f6e5957 100644 --- a/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt +++ b/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt @@ -40,4 +40,14 @@ suspend fun PerformanceRemote.toPerformanceWithPeople(service: MyServerService): service.getPerson(director_id).toPerson(), actorsList.toList() ) -} \ No newline at end of file +} + +fun Performance.toPerformanceRemote(): PerformanceRemote = PerformanceRemote( + performance_uid!!, + title, + description, + authorId!!, + directorId!!, + imageURL, + previewImageURL +) \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/api/performance/RestPerformanceRepository.kt b/app/src/main/java/com/example/mobile_labs/api/performance/RestPerformanceRepository.kt index 2725e5e..12bf2d5 100644 --- a/app/src/main/java/com/example/mobile_labs/api/performance/RestPerformanceRepository.kt +++ b/app/src/main/java/com/example/mobile_labs/api/performance/RestPerformanceRepository.kt @@ -6,6 +6,7 @@ import androidx.paging.PagingConfig import androidx.paging.PagingData import com.example.mobile_labs.api.MyServerService import com.example.mobile_labs.api.models.toPerformance +import com.example.mobile_labs.api.models.toPerformanceRemote import com.example.mobile_labs.api.models.toPerformanceWithPeople import com.example.mobile_labs.api.people.RestPersonRepository import com.example.mobile_labs.common.AppDataContainer @@ -50,4 +51,16 @@ class RestPerformanceRepository( override suspend fun getPerformance(uid: Int): PerformanceWithPeople = service.getPerformance(uid).toPerformanceWithPeople(service) + + override suspend fun insertPerformance(performance: Performance) { + service.createPerformance(performance.toPerformanceRemote()) + } + + override suspend fun updatePerformance(performance: Performance) { + service.updatePerformance(performance.performance_uid!!, performance.toPerformanceRemote()) + } + + override suspend fun deletePerformance(performance: Performance) { + service.deletePerformance(performance.performance_uid !!) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt b/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt index 9eafc96..3a04549 100644 --- a/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt +++ b/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt @@ -64,6 +64,6 @@ class AppDataContainer(val context: Context) : AppContainer { companion object { const val TIMEOUT = 5000L - const val LIMIT = 3 + const val LIMIT = 20 } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt b/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt index 2ca237d..5015d1b 100644 --- a/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt @@ -8,7 +8,9 @@ import androidx.lifecycle.viewmodel.viewModelFactory import com.example.mobile_labs.TheatreApplication import com.example.mobile_labs.ui.event.list.EventListViewModel import com.example.mobile_labs.ui.login.LoginViewModel +import com.example.mobile_labs.ui.performance.list.AdminPerformanceListViewModel import com.example.mobile_labs.ui.performance.list.PerformanceListViewModel +import com.example.mobile_labs.ui.performance.view.AdminPerformanceViewModel import com.example.mobile_labs.ui.performance.view.PerformanceViewModel import com.example.mobile_labs.ui.person.list.PeopleListViewModel @@ -29,6 +31,12 @@ object AppViewModelProvider { initializer { LoginViewModel() } + initializer { + AdminPerformanceListViewModel(theatreApplication().container.performanceRestRepository) + } + initializer { + AdminPerformanceViewModel(this.createSavedStateHandle(), theatreApplication().container.performanceRestRepository) + } } } diff --git a/app/src/main/java/com/example/mobile_labs/common/PerformanceRepository.kt b/app/src/main/java/com/example/mobile_labs/common/PerformanceRepository.kt index 65e2161..74c1332 100644 --- a/app/src/main/java/com/example/mobile_labs/common/PerformanceRepository.kt +++ b/app/src/main/java/com/example/mobile_labs/common/PerformanceRepository.kt @@ -8,4 +8,7 @@ import kotlinx.coroutines.flow.Flow interface PerformanceRepository { fun getAllPerformances(): Flow> suspend fun getPerformance(uid: Int): PerformanceWithPeople + suspend fun insertPerformance(performance: Performance) + suspend fun updatePerformance(performance: Performance) + suspend fun deletePerformance(performance: Performance) } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/database/performance/repository/OfflinePerformanceRepository.kt b/app/src/main/java/com/example/mobile_labs/database/performance/repository/OfflinePerformanceRepository.kt index a408aef..ba05990 100644 --- a/app/src/main/java/com/example/mobile_labs/database/performance/repository/OfflinePerformanceRepository.kt +++ b/app/src/main/java/com/example/mobile_labs/database/performance/repository/OfflinePerformanceRepository.kt @@ -24,11 +24,11 @@ class OfflinePerformanceRepository(private val performanceDao: PerformanceDao) : ).flow override suspend fun getPerformance(uid: Int): PerformanceWithPeople = performanceDao.getByUid(uid).first(); - suspend fun insertPerformance(performance: Performance) = performanceDao.insert(performance); + override suspend fun insertPerformance(performance: Performance) = performanceDao.insert(performance); - suspend fun updatePerformance(performance: Performance) = performanceDao.update(performance); + override suspend fun updatePerformance(performance: Performance) = performanceDao.update(performance); - suspend fun deletePerformance(performance: Performance) = performanceDao.delete(performance); + override suspend fun deletePerformance(performance: Performance) = performanceDao.delete(performance); suspend fun clearPerformances() = performanceDao.deleteAll() diff --git a/app/src/main/java/com/example/mobile_labs/ui/navigation/MainNavbar.kt b/app/src/main/java/com/example/mobile_labs/ui/navigation/MainNavbar.kt index ffb3b82..15857ef 100644 --- a/app/src/main/java/com/example/mobile_labs/ui/navigation/MainNavbar.kt +++ b/app/src/main/java/com/example/mobile_labs/ui/navigation/MainNavbar.kt @@ -1,6 +1,7 @@ package com.example.mobile_labs.ui.navigation import android.content.res.Configuration +import android.util.Log import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons @@ -37,7 +38,9 @@ import com.example.mobile_labs.api.MyServerService import com.example.mobile_labs.ui.about.About import com.example.mobile_labs.ui.event.list.EventList import com.example.mobile_labs.ui.login.Login +import com.example.mobile_labs.ui.performance.list.AdminPerformanceList import com.example.mobile_labs.ui.performance.list.PerformanceList +import com.example.mobile_labs.ui.performance.view.AdminPerformanceView import com.example.mobile_labs.ui.performance.view.PerformanceView import com.example.mobile_labs.ui.person.list.PeopleList @@ -79,8 +82,26 @@ fun Navbar( modifier: Modifier = Modifier ) { NavigationBar(modifier) { - Screen.bottomBarItems.forEach { screen -> - if (MyServerService.getToken().isBlank() || screen.route !== "login") { + if (MyServerService.getToken().isBlank()) { + Screen.bottomBarItems.forEach { screen -> + NavigationBarItem( + icon = { Icon(screen.icon, contentDescription = null) }, + label = { Text(stringResource(screen.resourceId)) }, + selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true, + onClick = { + navController.navigate(screen.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = false + } + launchSingleTop = true + restoreState = false + } + } + ) + } + } + else { + Screen.adminBottomBarItems.forEach { screen -> NavigationBarItem( icon = { Icon(screen.icon, contentDescription = null) }, label = { Text(stringResource(screen.resourceId)) }, @@ -113,7 +134,12 @@ fun Navhost( ) { composable(Screen.Schedule.route) { EventList(navController) } composable(Screen.Repertoire.route) { - PerformanceList(navController) + if (MyServerService.getToken().isNotBlank()) { + AdminPerformanceList(navController) + } + else { + PerformanceList(navController) + } } composable(Screen.PeopleList.route) { PeopleList() } composable(Screen.About.route) { About() } @@ -124,7 +150,12 @@ fun Navhost( Screen.PerformanceView.route, arguments = listOf(navArgument("id") { type = NavType.IntType }) ) { backStackEntry -> - backStackEntry.arguments?.let { PerformanceView() } + if (MyServerService.getToken().isNotBlank()) { + AdminPerformanceView(navController) + } + else { + backStackEntry.arguments?.let { PerformanceView() } + } } } } diff --git a/app/src/main/java/com/example/mobile_labs/ui/navigation/Screen.kt b/app/src/main/java/com/example/mobile_labs/ui/navigation/Screen.kt index d713ee2..4d6595c 100644 --- a/app/src/main/java/com/example/mobile_labs/ui/navigation/Screen.kt +++ b/app/src/main/java/com/example/mobile_labs/ui/navigation/Screen.kt @@ -38,16 +38,16 @@ enum class Screen( ); companion object { - val bottomBarItems = if (MyServerService.getToken().isBlank()) listOf( + val bottomBarItems = listOf( Schedule, Repertoire, PeopleList, About, Login - ) else listOf( - Schedule, + ) + + val adminBottomBarItems = listOf( Repertoire, - PeopleList, About, ) diff --git a/app/src/main/java/com/example/mobile_labs/ui/performance/list/AdminPerformanceList.kt b/app/src/main/java/com/example/mobile_labs/ui/performance/list/AdminPerformanceList.kt new file mode 100644 index 0000000..af7fc9d --- /dev/null +++ b/app/src/main/java/com/example/mobile_labs/ui/performance/list/AdminPerformanceList.kt @@ -0,0 +1,261 @@ +package com.example.mobile_labs.ui.performance.list + +import android.content.res.Configuration +import android.util.Log +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.spring +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DismissDirection +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.DismissState +import androidx.compose.material3.DismissValue +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SwipeToDismiss +import androidx.compose.material3.rememberDismissState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.paging.PagingData +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey +import coil.compose.AsyncImage +import com.example.mobile_labs.R +import com.example.mobile_labs.api.MyServerService +import com.example.mobile_labs.database.performance.model.Performance +import com.example.mobile_labs.common.AppViewModelProvider +import com.example.mobile_labs.ui.navigation.Screen +import com.example.mobile_labs.ui.theme.Mobile_LabsTheme +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch + +@Composable +fun AdminPerformanceList( + navController: NavController, + viewModel: AdminPerformanceListViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + val performanceListUiState = viewModel.performanceListUiState.collectAsLazyPagingItems() + Scaffold( + topBar = {}, + floatingActionButton = { + FloatingActionButton( + onClick = { + val route = Screen.PerformanceView.route.replace("{id}", 0.toString()) + navController.navigate(route) + }, + ) { + Icon(Icons.Filled.Add, "Добавить") + } + } + ) { innerPadding -> + PerformanceList( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + performanceList = performanceListUiState, + onClick = { uid: Int -> + val route = Screen.PerformanceView.route.replace("{id}", uid.toString()) + navController.navigate(route) + }, + onSwipe = { performance: Performance -> + coroutineScope.launch { + viewModel.deleteStudent(performance) + } + } + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DismissBackground(dismissState: DismissState) { + val color = when (dismissState.dismissDirection) { + DismissDirection.StartToEnd -> Color.Transparent + DismissDirection.EndToStart -> Color(0xFFFF1744) + null -> Color.Transparent + } + val direction = dismissState.dismissDirection + + Row( + modifier = Modifier + .fillMaxSize() + .background(color) + .padding(12.dp, 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + if (direction == DismissDirection.EndToStart) { + Icon( + Icons.Default.Delete, + contentDescription = "delete", + tint = Color.White + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SwipeToDelete( + dismissState: DismissState, + performance: Performance, + onClick: (uid: Int) -> Unit +) { + SwipeToDismiss( + modifier = Modifier.zIndex(1f), + state = dismissState, + directions = setOf( + DismissDirection.EndToStart + ), + background = { + DismissBackground(dismissState) + }, + dismissContent = { + PerformanceListItem(performance = performance, + modifier = Modifier + .padding(vertical = 7.dp) + .clickable { onClick(performance.performance_uid!!) }) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun PerformanceList( + modifier: Modifier = Modifier, + performanceList: LazyPagingItems, + onClick: (uid: Int) -> Unit, + onSwipe: (performance: Performance) -> Unit +) { + Column( + modifier = modifier + ) { + if (performanceList.itemCount == 0) { + Text( + text = stringResource(R.string.performance_missing_description), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge + ) + } else { + LazyColumn(modifier = Modifier.padding(all = 10.dp)) { + items( + count = performanceList.itemCount, + key = performanceList.itemKey(), + contentType = performanceList.itemContentType() + ) { index -> + val performance = performanceList[index] + performance?.let { + var show by remember { mutableStateOf(true) } + val dismissState = rememberDismissState( + confirmValueChange = { + if (it == DismissValue.DismissedToStart || + it == DismissValue.DismissedToEnd + ) { + show = false + true + } else false + }, positionalThreshold = { 200.dp.toPx() } + ) + + AnimatedVisibility( + show, exit = fadeOut(spring()) + ) { + SwipeToDelete( + dismissState = dismissState, + performance = performance, + onClick = onClick + ) + } + + LaunchedEffect(show) { + if (!show) { + delay(800) + onSwipe(performance) + } + } + } + } + } + } + } +} + +@Composable +private fun PerformanceListItem( + performance: Performance, + modifier: Modifier = Modifier, +) { + Card( + modifier = modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column( + modifier = modifier.padding(all = 10.dp) + ) { + AsyncImage(model = performance.imageURL, contentDescription = "") + Button( + modifier = Modifier + .fillMaxWidth() + .padding(all = 10.dp), + onClick = { }) { + Text(performance.title) + } + } + } +} + +@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun AdminSchedulePreview() { + Mobile_LabsTheme { + Surface( + color = MaterialTheme.colorScheme.background + ) { + PerformanceList( + performanceList = MutableStateFlow( + PagingData.empty() + ).collectAsLazyPagingItems(), + onClick = {}, + onSwipe = {} + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/ui/performance/list/AdminPerformanceListViewModel.kt b/app/src/main/java/com/example/mobile_labs/ui/performance/list/AdminPerformanceListViewModel.kt new file mode 100644 index 0000000..67ac2a1 --- /dev/null +++ b/app/src/main/java/com/example/mobile_labs/ui/performance/list/AdminPerformanceListViewModel.kt @@ -0,0 +1,17 @@ +package com.example.mobile_labs.ui.performance.list + +import androidx.lifecycle.ViewModel +import androidx.paging.PagingData +import com.example.mobile_labs.common.PerformanceRepository +import com.example.mobile_labs.database.performance.model.Performance +import kotlinx.coroutines.flow.Flow + +class AdminPerformanceListViewModel( + private val performanceRepository: PerformanceRepository +) : ViewModel() { + val performanceListUiState: Flow> = performanceRepository.getAllPerformances() + + suspend fun deleteStudent(performance: Performance) { + performanceRepository.deletePerformance(performance) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/ui/performance/view/AdminPerformanceView.kt b/app/src/main/java/com/example/mobile_labs/ui/performance/view/AdminPerformanceView.kt new file mode 100644 index 0000000..b1857d6 --- /dev/null +++ b/app/src/main/java/com/example/mobile_labs/ui/performance/view/AdminPerformanceView.kt @@ -0,0 +1,129 @@ +package com.example.mobile_labs.ui.performance.view + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import coil.compose.AsyncImage +import com.example.mobile_labs.R +import com.example.mobile_labs.common.AppViewModelProvider +import com.example.mobile_labs.ui.theme.Mobile_LabsTheme +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AdminPerformanceView( + navController: NavController, + viewModel: AdminPerformanceViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + PerformanceEdit( + performanceUiState = viewModel.performanceUiState, + onClick = { + coroutineScope.launch { + viewModel.savePerformance() + navController.popBackStack() + } + }, + onUpdate = viewModel::updateUiState, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun PerformanceEdit( + performanceUiState: AdminPerformanceUiState, + onClick: () -> Unit, + onUpdate: (AdminPerformanceDetails) -> Unit, +) { + Column( + Modifier + .fillMaxWidth() + .padding(all = 10.dp) + ) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = performanceUiState.performanceDetails.title, + onValueChange = { onUpdate(performanceUiState.performanceDetails.copy(title = it)) }, + label = {Text("Название")}, + singleLine = true + ) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = performanceUiState.performanceDetails.description, + onValueChange = { onUpdate(performanceUiState.performanceDetails.copy(description = it)) }, + label = {Text("Описание")}, + singleLine = true + ) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = performanceUiState.performanceDetails.imageURL, + onValueChange = { onUpdate(performanceUiState.performanceDetails.copy(imageURL = it)) }, + label = { Text("URL изображения") }, + singleLine = true, + ) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = performanceUiState.performanceDetails.previewImageUrl, + onValueChange = { onUpdate(performanceUiState.performanceDetails.copy(previewImageUrl = it)) }, + label = {Text("URL превью")}, + singleLine = true + ) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = performanceUiState.performanceDetails.authorId.toString(), + onValueChange = { onUpdate(performanceUiState.performanceDetails.copy(authorId = it.toInt())) }, + label = {Text("Автор")}, + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone) + ) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = performanceUiState.performanceDetails.directorId.toString(), + onValueChange = { onUpdate(performanceUiState.performanceDetails.copy(directorId = it.toInt())) }, + label = {Text("Режиссер")}, + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone) + ) + Button( + onClick = onClick, + enabled = performanceUiState.isEntryValid, + shape = MaterialTheme.shapes.small, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = "Сохранить") + } + } +} + +@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun AdminPerformanceViewPreview() { + Mobile_LabsTheme { + Surface( + color = MaterialTheme.colorScheme.background + ) { + PerformanceView() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/ui/performance/view/AdminPerformanceViewModel.kt b/app/src/main/java/com/example/mobile_labs/ui/performance/view/AdminPerformanceViewModel.kt new file mode 100644 index 0000000..4da3ed4 --- /dev/null +++ b/app/src/main/java/com/example/mobile_labs/ui/performance/view/AdminPerformanceViewModel.kt @@ -0,0 +1,103 @@ +package com.example.mobile_labs.ui.performance.view + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.mobile_labs.database.performance.model.PerformanceWithPeople +import com.example.mobile_labs.common.PerformanceRepository +import com.example.mobile_labs.database.performance.model.Performance +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +class AdminPerformanceViewModel( + savedStateHandle: SavedStateHandle, + private val performanceRepository: PerformanceRepository +) : ViewModel() { + + var performanceUiState by mutableStateOf(AdminPerformanceUiState()) + private set + + private val performanceUid: Int = checkNotNull(savedStateHandle["id"]) + + init { + viewModelScope.launch { + if (performanceUid > 0) { + performanceUiState = performanceRepository.getPerformance(performanceUid) + .toUiStateAdmin() + } + } + } + + fun updateUiState(performanceDetails: AdminPerformanceDetails) { + performanceUiState = AdminPerformanceUiState( + performanceDetails = performanceDetails, + isEntryValid = validateInput(performanceDetails) + ) + } + + suspend fun savePerformance() { + if (validateInput()) { + if (performanceUid > 0) { + performanceRepository.updatePerformance( + performanceUiState.performanceDetails.toPerformance(performanceUid) + ) + } else { + performanceRepository.insertPerformance( + performanceUiState.performanceDetails.toPerformance() + ) + } + } + } + + private fun validateInput(uiState: AdminPerformanceDetails = performanceUiState.performanceDetails): Boolean { + return with(uiState) { + title.isNotBlank() + && description.isNotBlank() + } + } +} + +data class AdminPerformanceUiState( + val performanceDetails: AdminPerformanceDetails = AdminPerformanceDetails(), + val isEntryValid: Boolean = false +) + +data class AdminPerformanceDetails( + val title: String = "", + val authorName: String = "", + val actorsList: String = "", + val imageURL: String = "", + val description: String = "", + val authorId: Int = 0, + val directorId: Int = 0, + val previewImageUrl: String = "", +) + +fun PerformanceWithPeople.toAdminDetails(): AdminPerformanceDetails = AdminPerformanceDetails( + title = performance.title, + authorName = String.format("%s %s", author.last_name, author.first_name), + actorsList = buildString { for (actor in actors) append(actor.last_name + " " + actor.first_name + "\n") }, + imageURL = performance.imageURL, + description = performance.description, + authorId = performance.authorId!!, + directorId = performance.directorId!!, + previewImageUrl = performance.previewImageURL, +) + +fun PerformanceWithPeople.toUiStateAdmin(): AdminPerformanceUiState = AdminPerformanceUiState( + performanceDetails = this.toAdminDetails(), +) + +fun AdminPerformanceDetails.toPerformance(uid: Int = 0): Performance = Performance( + performance_uid = uid, + title = title, + authorId = authorId, + directorId = directorId, + imageURL = imageURL, + description = description, + previewImageURL = previewImageUrl +) \ No newline at end of file