Compare commits

..

3 Commits

Author SHA1 Message Date
abazov73
2eca8db087 Course work: implement full performance CRUD 2023-12-27 01:21:02 +04:00
abazov73
a687ff002f Course work: implement login 2023-12-26 23:33:11 +04:00
abazov73
d79f6982fe Course work: edit routes for new server 2023-12-26 18:55:30 +04:00
25 changed files with 900 additions and 45 deletions

View File

@ -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"
@ -94,4 +95,7 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("javax.inject:javax.inject:1")
implementation("androidx.compose.runtime:runtime-livedata")
}

View File

@ -1,8 +1,11 @@
package com.example.mobile_labs.api
import android.util.Log
import com.example.mobile_labs.api.models.Credentials
import com.example.mobile_labs.api.models.EventRemote
import com.example.mobile_labs.api.models.PerformanceRemote
import com.example.mobile_labs.api.models.PersonRemote
import com.example.mobile_labs.api.models.Token
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
@ -16,53 +19,96 @@ import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
import java.util.concurrent.TimeUnit
interface MyServerService {
@GET("persons")
@GET("api/persons")
suspend fun getPeople(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<PersonRemote>
@GET("performances")
@GET("api/performances")
suspend fun getPerformances(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<PerformanceRemote>
@GET("events")
@GET("api/events")
suspend fun getEvents(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<EventRemote>
@GET("performances/{id}")
@GET("api/performances/{id}")
suspend fun getPerformance(
@Path("id") id: Int,
): PerformanceRemote
@GET("persons/{id}")
@GET("api/persons/{id}")
suspend fun getPerson(
@Path("id") id: Int,
): PersonRemote
@GET("events/{id}")
@GET("api/events/{id}")
suspend fun getEvent(
@Path("id") id: Int,
): EventRemote
@POST("api/login")
suspend fun login(
@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:26000/"
private const val BASE_URL = "http://10.0.2.2:8000/"
@Volatile
private var INSTANCE: MyServerService? = null
private var _token: String = ""
fun getInstance(): MyServerService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.connectTimeout(1, TimeUnit.DAYS)
.readTimeout(1, TimeUnit.DAYS)
.writeTimeout(1, TimeUnit.DAYS)
.retryOnConnectionFailure(false)
.callTimeout(1, TimeUnit.DAYS)
.addInterceptor(logger)
.addInterceptor {
val originalRequest = it.request()
if (_token.isEmpty()) {
it.proceed(originalRequest)
} else {
it.proceed(
originalRequest
.newBuilder()
.header("Authorization", "Bearer $_token")
.method(originalRequest.method, originalRequest.body)
.build()
)
}
}
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
@ -73,5 +119,13 @@ interface MyServerService {
.also { INSTANCE = it }
}
}
fun getToken(): String {
return _token
}
fun setToken(token: String) {
_token = token
}
}
}

View File

@ -73,6 +73,9 @@ class EventRemoteMediator(
nextKey = nextKey
)
}
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.EVENT)
dbEventRepository.clearEvents()
performanceRestRepository.getAllPerformances()
dbRemoteKeyRepository.createRemoteKeys(keys)
dbEventRepository.insertEvents(events)

View File

@ -0,0 +1,9 @@
package com.example.mobile_labs.api.models
import kotlinx.serialization.Serializable
@Serializable
data class Credentials(
val email: String = "",
val password: String = "",
)

View File

@ -10,16 +10,16 @@ import java.time.LocalDate
data class EventRemote(
val id: Int = 0,
val date: String = "",
val performanceId: Int = 0,
val performance_id: Int = 0,
)
fun EventRemote.toEvent(): Event = Event(
id,
LocalDate.parse(date),
performanceId
performance_id
)
suspend fun EventRemote.toEventWithPerformance(service: MyServerService): EventWithPerformance = EventWithPerformance(
service.getEvent(id).toEvent(),
service.getPerformance(performanceId).toPerformance()
service.getPerformance(performance_id).toPerformance()
)

View File

@ -11,10 +11,10 @@ data class PerformanceRemote(
val id: Int = 0,
val title: String = "",
val description: String = "",
val authorId: Int = 0,
val directorId: Int = 0,
val imageURL: String = "",
val previewImageURL: String = "",
val author_id: Int = 0,
val director_id: Int = 0,
val image_url: String = "",
val preview_image_url: String = "",
val actors: List<Int> = listOf()
)
@ -22,10 +22,10 @@ fun PerformanceRemote.toPerformance(): Performance = Performance(
id,
title,
description,
authorId,
directorId,
imageURL,
previewImageURL
author_id,
director_id,
image_url,
preview_image_url
)
suspend fun PerformanceRemote.toPerformanceWithPeople(service: MyServerService): PerformanceWithPeople {
@ -36,8 +36,18 @@ suspend fun PerformanceRemote.toPerformanceWithPeople(service: MyServerService):
return PerformanceWithPeople(
service.getPerformance(id).toPerformance(),
service.getPerson(authorId).toPerson(),
service.getPerson(directorId).toPerson(),
service.getPerson(author_id).toPerson(),
service.getPerson(director_id).toPerson(),
actorsList.toList()
)
}
fun Performance.toPerformanceRemote(): PerformanceRemote = PerformanceRemote(
performance_uid!!,
title,
description,
authorId!!,
directorId!!,
imageURL,
previewImageURL
)

View File

@ -10,12 +10,12 @@ data class PersonRemote(
val id: Int = 0,
val last_name: String = "",
val first_name: String = "",
val imageURL: String = ""
val image_url: String = ""
)
fun PersonRemote.toPerson(): Person = Person(
id,
last_name,
first_name,
imageURL
image_url
)

View File

@ -0,0 +1,8 @@
package com.example.mobile_labs.api.models
import kotlinx.serialization.Serializable
@Serializable
data class Token(
val token: String
)

View File

@ -71,6 +71,9 @@ class PeopleRemoteMediator(
nextKey = nextKey
)
}
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERSON)
dbPersonRepository.clearPeople()
dbRemoteKeyRepository.createRemoteKeys(keys)
dbPersonRepository.insertPeople(people)
}

View File

@ -1,9 +1,11 @@
package com.example.mobile_labs.api.performance
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.paging.log
import androidx.room.withTransaction
import com.example.mobile_labs.api.MyServerService
import com.example.mobile_labs.api.models.toPerformance
@ -73,6 +75,9 @@ class PerformanceRemoteMediator(
nextKey = nextKey
)
}
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERFORMANCE)
dbPerformanceRepository.clearPerformances()
personRestRepository.getAllPeople()
dbRemoteKeyRepository.createRemoteKeys(keys)
dbPerformanceRepository.insertPerformances(performances)

View File

@ -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 !!)
}
}

View File

@ -64,6 +64,6 @@ class AppDataContainer(val context: Context) : AppContainer {
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 3
const val LIMIT = 20
}
}

View File

@ -7,7 +7,10 @@ import androidx.lifecycle.viewmodel.initializer
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
@ -25,6 +28,15 @@ object AppViewModelProvider {
initializer {
PerformanceViewModel(this.createSavedStateHandle(), theatreApplication().container.performanceRestRepository)
}
initializer {
LoginViewModel()
}
initializer {
AdminPerformanceListViewModel(theatreApplication().container.performanceRestRepository)
}
initializer {
AdminPerformanceViewModel(this.createSavedStateHandle(), theatreApplication().container.performanceRestRepository)
}
}
}

View File

@ -8,4 +8,7 @@ import kotlinx.coroutines.flow.Flow
interface PerformanceRepository {
fun getAllPerformances(): Flow<PagingData<Performance>>
suspend fun getPerformance(uid: Int): PerformanceWithPeople
suspend fun insertPerformance(performance: Performance)
suspend fun updatePerformance(performance: Performance)
suspend fun deletePerformance(performance: Performance)
}

View File

@ -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()

View File

@ -0,0 +1,116 @@
package com.example.mobile_labs.ui.login
import androidx.compose.foundation.layout.*
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.*
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.mobile_labs.R
import com.example.mobile_labs.common.AppViewModelProvider
import com.example.mobile_labs.ui.navigation.Screen
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun Login(
navController: NavHostController,
viewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val success = viewModel.successState.observeAsState().value
val login = remember { mutableStateOf(TextFieldValue(""))}
val password = remember { mutableStateOf(TextFieldValue(""))}
if (success == false) {
AlertDialog(
title = {
Text(
text = "Ошибка авторизации",
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
},
text = {
Text(
text = "Неверный логин или пароль",
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
},
onDismissRequest = {
viewModel.calmSuccessState()
},
confirmButton = {
TextButton(
onClick = {
viewModel.calmSuccessState()
}
) {
Text("OK")
}
}
)
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth(0.7f).fillMaxHeight().padding(vertical=100.dp)
) {
Text(
text = "Вход",
style = TextStyle(
fontSize = 40.sp,
fontWeight = FontWeight(400),
),
modifier = Modifier.fillMaxHeight(0.3f)
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth().padding(bottom=30.dp),
value = login.value,
onValueChange = { login.value = it },
label = { Text(stringResource(id = R.string.login)) },
singleLine = true
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth().padding(bottom=30.dp),
value = password.value,
onValueChange = { password.value = it },
label = { Text(stringResource(id = R.string.password)) },
singleLine = true
)
Button(
{
viewModel.login(login.value.text, password.value.text, navController)
},
modifier = Modifier.padding(bottom=20.dp).fillMaxWidth(0.5f)
) {
Text(
text = "Войти",
style = TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight(400),
color = Color(0xFFFFFFFF),
)
)
}
}
}
}

View File

@ -0,0 +1,49 @@
package com.example.mobile_labs.ui.login
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import com.example.mobile_labs.api.MyServerService
import com.example.mobile_labs.api.models.Credentials
import com.example.mobile_labs.ui.navigation.Screen
import kotlinx.coroutines.launch
import javax.inject.Inject
class LoginViewModel : ViewModel() {
private val _successState = MutableLiveData<Boolean?>()
val successState: LiveData<Boolean?>
get() = _successState
fun calmSuccessState() {
_successState.postValue(null)
}
fun login(login: String, password: String, navController: NavHostController) {
viewModelScope.launch {
val token = MyServerService.getInstance().login(Credentials(login, password))
if (token.token.isEmpty()) {
_successState.postValue(false)
return@launch
}
MyServerService.setToken(token.token)
_successState.setValue(true)
navigate(navController)
}
}
fun navigate(navController: NavHostController) {
Screen.About.route.let {
navController.navigate(it) {
popUpTo(it) {
inclusive = true
}
}
}
}
}

View File

@ -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
@ -33,9 +34,13 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.mobile_labs.ui.theme.Mobile_LabsTheme
import com.example.mobile_labs.R
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
@ -77,6 +82,7 @@ fun Navbar(
modifier: Modifier = Modifier
) {
NavigationBar(modifier) {
if (MyServerService.getToken().isBlank()) {
Screen.bottomBarItems.forEach { screen ->
NavigationBarItem(
icon = { Icon(screen.icon, contentDescription = null) },
@ -85,15 +91,34 @@ fun Navbar(
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
saveState = false
}
launchSingleTop = true
restoreState = true
restoreState = false
}
}
)
}
}
else {
Screen.adminBottomBarItems.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
}
}
)
}
}
}
}
@Composable
@ -108,16 +133,31 @@ fun Navhost(
modifier.padding(innerPadding)
) {
composable(Screen.Schedule.route) { EventList(navController) }
composable(Screen.Repertoire.route) { PerformanceList(navController) }
composable(Screen.Repertoire.route) {
if (MyServerService.getToken().isNotBlank()) {
AdminPerformanceList(navController)
}
else {
PerformanceList(navController)
}
}
composable(Screen.PeopleList.route) { PeopleList() }
composable(Screen.About.route) { About() }
if (MyServerService.getToken().isBlank()) {
composable(Screen.Login.route) { Login(navController) }
}
composable(
Screen.PerformanceView.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
if (MyServerService.getToken().isNotBlank()) {
AdminPerformanceView(navController)
}
else {
backStackEntry.arguments?.let { PerformanceView() }
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)

View File

@ -3,12 +3,14 @@ package com.example.mobile_labs.ui.navigation
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Face
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.List
import androidx.compose.material.icons.filled.Person
import androidx.compose.ui.graphics.vector.ImageVector
import com.example.mobile_labs.R
import com.example.mobile_labs.api.MyServerService
enum class Screen(
val route: String,
@ -30,6 +32,9 @@ enum class Screen(
),
About(
"about", R.string.about_main_title, Icons.Filled.Info
),
Login(
"login", R.string.login_main_title, Icons.Filled.Face
);
companion object {
@ -38,6 +43,12 @@ enum class Screen(
Repertoire,
PeopleList,
About,
Login
)
val adminBottomBarItems = listOf(
Repertoire,
About,
)
fun getItem(route: String): Screen? {

View File

@ -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<Performance>,
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<Performance>()
).collectAsLazyPagingItems(),
onClick = {},
onSwipe = {}
)
}
}
}

View File

@ -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<PagingData<Performance>> = performanceRepository.getAllPerformances()
suspend fun deleteStudent(performance: Performance) {
performanceRepository.deletePerformance(performance)
}
}

View File

@ -1,6 +1,7 @@
package com.example.mobile_labs.ui.performance.list
import android.content.res.Configuration
import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -28,6 +29,7 @@ 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

View File

@ -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()
}
}
}

View File

@ -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
)

View File

@ -6,12 +6,15 @@
<string name="performance_author">Автор</string>
<string name="performance_director">Режиссёр</string>
<string name="performance_actors">Актёры</string>
<string name="people_main_title">Люди театра</string>
<string name="people_main_title">Труппа</string>
<string name="about_main_title">О нас</string>
<string name="login_main_title">Вход</string>
<string name="performance_view_main_title">Представление</string>
<string name="people_missing_description">Данные о людях отсутствуют</string>
<string name="events_missing_description">Данные о событиях отсутствуют</string>
<string name="performance_missing_description">Данные о представлениях отсутствуют</string>
<string name="login">Логин</string>
<string name="password">Пароль</string>
<string name="about_text">
<p>
Netus quis congue nascetur ullamcorper nibh, nostra iaculis turpis! Facilisi pretium vehicula purus porttitor vitae aliquet dignissim. Donec diam molestie litora magnis dolor aptent scelerisque mus. Sit mi, venenatis interdum. Commodo vel malesuada tincidunt eget. Aenean laoreet lacinia platea sem? Libero urna odio diam? Nisl, sodales nisi gravida. Interdum elementum libero turpis dapibus tristique per sed maecenas ante integer massa? Tortor molestie sapien himenaeos condimentum. Facilisis accumsan ullamcorper semper fermentum elementum quisque. Curae;, vivamus ante hac elit fringilla odio ornare curabitur quisque magna commodo. Placerat proin!