Compare commits
3 Commits
6b3fc223c1
...
2eca8db087
Author | SHA1 | Date | |
---|---|---|---|
|
2eca8db087 | ||
|
a687ff002f | ||
|
d79f6982fe |
@ -67,7 +67,8 @@ dependencies {
|
|||||||
implementation("androidx.compose.ui:ui")
|
implementation("androidx.compose.ui:ui")
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
implementation("androidx.compose.ui:ui-graphics")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
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
|
// Room
|
||||||
val room_version = "2.5.2"
|
val room_version = "2.5.2"
|
||||||
@ -94,4 +95,7 @@ dependencies {
|
|||||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||||
|
|
||||||
|
implementation("javax.inject:javax.inject:1")
|
||||||
|
implementation("androidx.compose.runtime:runtime-livedata")
|
||||||
}
|
}
|
@ -1,8 +1,11 @@
|
|||||||
package com.example.mobile_labs.api
|
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.EventRemote
|
||||||
import com.example.mobile_labs.api.models.PerformanceRemote
|
import com.example.mobile_labs.api.models.PerformanceRemote
|
||||||
import com.example.mobile_labs.api.models.PersonRemote
|
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 com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
@ -16,53 +19,96 @@ import retrofit2.http.POST
|
|||||||
import retrofit2.http.PUT
|
import retrofit2.http.PUT
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
interface MyServerService {
|
interface MyServerService {
|
||||||
@GET("persons")
|
@GET("api/persons")
|
||||||
suspend fun getPeople(
|
suspend fun getPeople(
|
||||||
@Query("_page") page: Int,
|
@Query("_page") page: Int,
|
||||||
@Query("_limit") limit: Int,
|
@Query("_limit") limit: Int,
|
||||||
): List<PersonRemote>
|
): List<PersonRemote>
|
||||||
|
|
||||||
@GET("performances")
|
@GET("api/performances")
|
||||||
suspend fun getPerformances(
|
suspend fun getPerformances(
|
||||||
@Query("_page") page: Int,
|
@Query("_page") page: Int,
|
||||||
@Query("_limit") limit: Int,
|
@Query("_limit") limit: Int,
|
||||||
): List<PerformanceRemote>
|
): List<PerformanceRemote>
|
||||||
|
|
||||||
@GET("events")
|
@GET("api/events")
|
||||||
suspend fun getEvents(
|
suspend fun getEvents(
|
||||||
@Query("_page") page: Int,
|
@Query("_page") page: Int,
|
||||||
@Query("_limit") limit: Int,
|
@Query("_limit") limit: Int,
|
||||||
): List<EventRemote>
|
): List<EventRemote>
|
||||||
|
|
||||||
@GET("performances/{id}")
|
@GET("api/performances/{id}")
|
||||||
suspend fun getPerformance(
|
suspend fun getPerformance(
|
||||||
@Path("id") id: Int,
|
@Path("id") id: Int,
|
||||||
): PerformanceRemote
|
): PerformanceRemote
|
||||||
|
|
||||||
@GET("persons/{id}")
|
@GET("api/persons/{id}")
|
||||||
suspend fun getPerson(
|
suspend fun getPerson(
|
||||||
@Path("id") id: Int,
|
@Path("id") id: Int,
|
||||||
): PersonRemote
|
): PersonRemote
|
||||||
|
|
||||||
@GET("events/{id}")
|
@GET("api/events/{id}")
|
||||||
suspend fun getEvent(
|
suspend fun getEvent(
|
||||||
@Path("id") id: Int,
|
@Path("id") id: Int,
|
||||||
): EventRemote
|
): 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 {
|
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
|
@Volatile
|
||||||
private var INSTANCE: MyServerService? = null
|
private var INSTANCE: MyServerService? = null
|
||||||
|
|
||||||
|
private var _token: String = ""
|
||||||
|
|
||||||
fun getInstance(): MyServerService {
|
fun getInstance(): MyServerService {
|
||||||
return INSTANCE ?: synchronized(this) {
|
return INSTANCE ?: synchronized(this) {
|
||||||
val logger = HttpLoggingInterceptor()
|
val logger = HttpLoggingInterceptor()
|
||||||
logger.level = HttpLoggingInterceptor.Level.BASIC
|
logger.level = HttpLoggingInterceptor.Level.BASIC
|
||||||
val client = OkHttpClient.Builder()
|
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(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()
|
.build()
|
||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
.baseUrl(BASE_URL)
|
.baseUrl(BASE_URL)
|
||||||
@ -73,5 +119,13 @@ interface MyServerService {
|
|||||||
.also { INSTANCE = it }
|
.also { INSTANCE = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getToken(): String {
|
||||||
|
return _token
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setToken(token: String) {
|
||||||
|
_token = token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -73,6 +73,9 @@ class EventRemoteMediator(
|
|||||||
nextKey = nextKey
|
nextKey = nextKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.EVENT)
|
||||||
|
dbEventRepository.clearEvents()
|
||||||
|
|
||||||
performanceRestRepository.getAllPerformances()
|
performanceRestRepository.getAllPerformances()
|
||||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||||
dbEventRepository.insertEvents(events)
|
dbEventRepository.insertEvents(events)
|
||||||
|
@ -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 = "",
|
||||||
|
)
|
@ -10,16 +10,16 @@ import java.time.LocalDate
|
|||||||
data class EventRemote(
|
data class EventRemote(
|
||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val date: String = "",
|
val date: String = "",
|
||||||
val performanceId: Int = 0,
|
val performance_id: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun EventRemote.toEvent(): Event = Event(
|
fun EventRemote.toEvent(): Event = Event(
|
||||||
id,
|
id,
|
||||||
LocalDate.parse(date),
|
LocalDate.parse(date),
|
||||||
performanceId
|
performance_id
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun EventRemote.toEventWithPerformance(service: MyServerService): EventWithPerformance = EventWithPerformance(
|
suspend fun EventRemote.toEventWithPerformance(service: MyServerService): EventWithPerformance = EventWithPerformance(
|
||||||
service.getEvent(id).toEvent(),
|
service.getEvent(id).toEvent(),
|
||||||
service.getPerformance(performanceId).toPerformance()
|
service.getPerformance(performance_id).toPerformance()
|
||||||
)
|
)
|
@ -11,10 +11,10 @@ data class PerformanceRemote(
|
|||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val title: String = "",
|
val title: String = "",
|
||||||
val description: String = "",
|
val description: String = "",
|
||||||
val authorId: Int = 0,
|
val author_id: Int = 0,
|
||||||
val directorId: Int = 0,
|
val director_id: Int = 0,
|
||||||
val imageURL: String = "",
|
val image_url: String = "",
|
||||||
val previewImageURL: String = "",
|
val preview_image_url: String = "",
|
||||||
val actors: List<Int> = listOf()
|
val actors: List<Int> = listOf()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,10 +22,10 @@ fun PerformanceRemote.toPerformance(): Performance = Performance(
|
|||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
authorId,
|
author_id,
|
||||||
directorId,
|
director_id,
|
||||||
imageURL,
|
image_url,
|
||||||
previewImageURL
|
preview_image_url
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun PerformanceRemote.toPerformanceWithPeople(service: MyServerService): PerformanceWithPeople {
|
suspend fun PerformanceRemote.toPerformanceWithPeople(service: MyServerService): PerformanceWithPeople {
|
||||||
@ -36,8 +36,18 @@ suspend fun PerformanceRemote.toPerformanceWithPeople(service: MyServerService):
|
|||||||
|
|
||||||
return PerformanceWithPeople(
|
return PerformanceWithPeople(
|
||||||
service.getPerformance(id).toPerformance(),
|
service.getPerformance(id).toPerformance(),
|
||||||
service.getPerson(authorId).toPerson(),
|
service.getPerson(author_id).toPerson(),
|
||||||
service.getPerson(directorId).toPerson(),
|
service.getPerson(director_id).toPerson(),
|
||||||
actorsList.toList()
|
actorsList.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Performance.toPerformanceRemote(): PerformanceRemote = PerformanceRemote(
|
||||||
|
performance_uid!!,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
authorId!!,
|
||||||
|
directorId!!,
|
||||||
|
imageURL,
|
||||||
|
previewImageURL
|
||||||
|
)
|
@ -10,12 +10,12 @@ data class PersonRemote(
|
|||||||
val id: Int = 0,
|
val id: Int = 0,
|
||||||
val last_name: String = "",
|
val last_name: String = "",
|
||||||
val first_name: String = "",
|
val first_name: String = "",
|
||||||
val imageURL: String = ""
|
val image_url: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
fun PersonRemote.toPerson(): Person = Person(
|
fun PersonRemote.toPerson(): Person = Person(
|
||||||
id,
|
id,
|
||||||
last_name,
|
last_name,
|
||||||
first_name,
|
first_name,
|
||||||
imageURL
|
image_url
|
||||||
)
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.example.mobile_labs.api.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Token(
|
||||||
|
val token: String
|
||||||
|
)
|
@ -71,6 +71,9 @@ class PeopleRemoteMediator(
|
|||||||
nextKey = nextKey
|
nextKey = nextKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERSON)
|
||||||
|
dbPersonRepository.clearPeople()
|
||||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||||
dbPersonRepository.insertPeople(people)
|
dbPersonRepository.insertPeople(people)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package com.example.mobile_labs.api.performance
|
package com.example.mobile_labs.api.performance
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.paging.ExperimentalPagingApi
|
import androidx.paging.ExperimentalPagingApi
|
||||||
import androidx.paging.LoadType
|
import androidx.paging.LoadType
|
||||||
import androidx.paging.PagingState
|
import androidx.paging.PagingState
|
||||||
import androidx.paging.RemoteMediator
|
import androidx.paging.RemoteMediator
|
||||||
|
import androidx.paging.log
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import com.example.mobile_labs.api.MyServerService
|
import com.example.mobile_labs.api.MyServerService
|
||||||
import com.example.mobile_labs.api.models.toPerformance
|
import com.example.mobile_labs.api.models.toPerformance
|
||||||
@ -73,6 +75,9 @@ class PerformanceRemoteMediator(
|
|||||||
nextKey = nextKey
|
nextKey = nextKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERFORMANCE)
|
||||||
|
dbPerformanceRepository.clearPerformances()
|
||||||
|
|
||||||
personRestRepository.getAllPeople()
|
personRestRepository.getAllPeople()
|
||||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||||
dbPerformanceRepository.insertPerformances(performances)
|
dbPerformanceRepository.insertPerformances(performances)
|
||||||
|
@ -6,6 +6,7 @@ import androidx.paging.PagingConfig
|
|||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import com.example.mobile_labs.api.MyServerService
|
import com.example.mobile_labs.api.MyServerService
|
||||||
import com.example.mobile_labs.api.models.toPerformance
|
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.models.toPerformanceWithPeople
|
||||||
import com.example.mobile_labs.api.people.RestPersonRepository
|
import com.example.mobile_labs.api.people.RestPersonRepository
|
||||||
import com.example.mobile_labs.common.AppDataContainer
|
import com.example.mobile_labs.common.AppDataContainer
|
||||||
@ -50,4 +51,16 @@ class RestPerformanceRepository(
|
|||||||
|
|
||||||
override suspend fun getPerformance(uid: Int): PerformanceWithPeople =
|
override suspend fun getPerformance(uid: Int): PerformanceWithPeople =
|
||||||
service.getPerformance(uid).toPerformanceWithPeople(service)
|
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 !!)
|
||||||
|
}
|
||||||
}
|
}
|
@ -64,6 +64,6 @@ class AppDataContainer(val context: Context) : AppContainer {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TIMEOUT = 5000L
|
const val TIMEOUT = 5000L
|
||||||
const val LIMIT = 3
|
const val LIMIT = 20
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,7 +7,10 @@ import androidx.lifecycle.viewmodel.initializer
|
|||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import com.example.mobile_labs.TheatreApplication
|
import com.example.mobile_labs.TheatreApplication
|
||||||
import com.example.mobile_labs.ui.event.list.EventListViewModel
|
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.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.performance.view.PerformanceViewModel
|
||||||
import com.example.mobile_labs.ui.person.list.PeopleListViewModel
|
import com.example.mobile_labs.ui.person.list.PeopleListViewModel
|
||||||
|
|
||||||
@ -25,6 +28,15 @@ object AppViewModelProvider {
|
|||||||
initializer {
|
initializer {
|
||||||
PerformanceViewModel(this.createSavedStateHandle(), theatreApplication().container.performanceRestRepository)
|
PerformanceViewModel(this.createSavedStateHandle(), theatreApplication().container.performanceRestRepository)
|
||||||
}
|
}
|
||||||
|
initializer {
|
||||||
|
LoginViewModel()
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
AdminPerformanceListViewModel(theatreApplication().container.performanceRestRepository)
|
||||||
|
}
|
||||||
|
initializer {
|
||||||
|
AdminPerformanceViewModel(this.createSavedStateHandle(), theatreApplication().container.performanceRestRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,4 +8,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
interface PerformanceRepository {
|
interface PerformanceRepository {
|
||||||
fun getAllPerformances(): Flow<PagingData<Performance>>
|
fun getAllPerformances(): Flow<PagingData<Performance>>
|
||||||
suspend fun getPerformance(uid: Int): PerformanceWithPeople
|
suspend fun getPerformance(uid: Int): PerformanceWithPeople
|
||||||
|
suspend fun insertPerformance(performance: Performance)
|
||||||
|
suspend fun updatePerformance(performance: Performance)
|
||||||
|
suspend fun deletePerformance(performance: Performance)
|
||||||
}
|
}
|
@ -24,11 +24,11 @@ class OfflinePerformanceRepository(private val performanceDao: PerformanceDao) :
|
|||||||
).flow
|
).flow
|
||||||
override suspend fun getPerformance(uid: Int): PerformanceWithPeople = performanceDao.getByUid(uid).first();
|
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()
|
suspend fun clearPerformances() = performanceDao.deleteAll()
|
||||||
|
|
||||||
|
116
app/src/main/java/com/example/mobile_labs/ui/login/Login.kt
Normal file
116
app/src/main/java/com/example/mobile_labs/ui/login/Login.kt
Normal 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),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.example.mobile_labs.ui.navigation
|
package com.example.mobile_labs.ui.navigation
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -33,9 +34,13 @@ import androidx.navigation.compose.rememberNavController
|
|||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.example.mobile_labs.ui.theme.Mobile_LabsTheme
|
import com.example.mobile_labs.ui.theme.Mobile_LabsTheme
|
||||||
import com.example.mobile_labs.R
|
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.about.About
|
||||||
import com.example.mobile_labs.ui.event.list.EventList
|
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.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.performance.view.PerformanceView
|
||||||
import com.example.mobile_labs.ui.person.list.PeopleList
|
import com.example.mobile_labs.ui.person.list.PeopleList
|
||||||
|
|
||||||
@ -77,21 +82,41 @@ fun Navbar(
|
|||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
NavigationBar(modifier) {
|
NavigationBar(modifier) {
|
||||||
Screen.bottomBarItems.forEach { screen ->
|
if (MyServerService.getToken().isBlank()) {
|
||||||
NavigationBarItem(
|
Screen.bottomBarItems.forEach { screen ->
|
||||||
icon = { Icon(screen.icon, contentDescription = null) },
|
NavigationBarItem(
|
||||||
label = { Text(stringResource(screen.resourceId)) },
|
icon = { Icon(screen.icon, contentDescription = null) },
|
||||||
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
|
label = { Text(stringResource(screen.resourceId)) },
|
||||||
onClick = {
|
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
|
||||||
navController.navigate(screen.route) {
|
onClick = {
|
||||||
popUpTo(navController.graph.findStartDestination().id) {
|
navController.navigate(screen.route) {
|
||||||
saveState = true
|
popUpTo(navController.graph.findStartDestination().id) {
|
||||||
|
saveState = false
|
||||||
|
}
|
||||||
|
launchSingleTop = true
|
||||||
|
restoreState = false
|
||||||
}
|
}
|
||||||
launchSingleTop = true
|
|
||||||
restoreState = true
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,14 +133,29 @@ fun Navhost(
|
|||||||
modifier.padding(innerPadding)
|
modifier.padding(innerPadding)
|
||||||
) {
|
) {
|
||||||
composable(Screen.Schedule.route) { EventList(navController) }
|
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.PeopleList.route) { PeopleList() }
|
||||||
composable(Screen.About.route) { About() }
|
composable(Screen.About.route) { About() }
|
||||||
|
if (MyServerService.getToken().isBlank()) {
|
||||||
|
composable(Screen.Login.route) { Login(navController) }
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
Screen.PerformanceView.route,
|
Screen.PerformanceView.route,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||||
) { backStackEntry ->
|
) { backStackEntry ->
|
||||||
backStackEntry.arguments?.let { PerformanceView() }
|
if (MyServerService.getToken().isNotBlank()) {
|
||||||
|
AdminPerformanceView(navController)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
backStackEntry.arguments?.let { PerformanceView() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,14 @@ package com.example.mobile_labs.ui.navigation
|
|||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.DateRange
|
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.Favorite
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.material.icons.filled.List
|
import androidx.compose.material.icons.filled.List
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import com.example.mobile_labs.R
|
import com.example.mobile_labs.R
|
||||||
|
import com.example.mobile_labs.api.MyServerService
|
||||||
|
|
||||||
enum class Screen(
|
enum class Screen(
|
||||||
val route: String,
|
val route: String,
|
||||||
@ -30,6 +32,9 @@ enum class Screen(
|
|||||||
),
|
),
|
||||||
About(
|
About(
|
||||||
"about", R.string.about_main_title, Icons.Filled.Info
|
"about", R.string.about_main_title, Icons.Filled.Info
|
||||||
|
),
|
||||||
|
Login(
|
||||||
|
"login", R.string.login_main_title, Icons.Filled.Face
|
||||||
);
|
);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -38,6 +43,12 @@ enum class Screen(
|
|||||||
Repertoire,
|
Repertoire,
|
||||||
PeopleList,
|
PeopleList,
|
||||||
About,
|
About,
|
||||||
|
Login
|
||||||
|
)
|
||||||
|
|
||||||
|
val adminBottomBarItems = listOf(
|
||||||
|
Repertoire,
|
||||||
|
About,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getItem(route: String): Screen? {
|
fun getItem(route: String): Screen? {
|
||||||
|
@ -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 = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.example.mobile_labs.ui.performance.list
|
package com.example.mobile_labs.ui.performance.list
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -28,6 +29,7 @@ import androidx.paging.compose.itemContentType
|
|||||||
import androidx.paging.compose.itemKey
|
import androidx.paging.compose.itemKey
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.example.mobile_labs.R
|
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.database.performance.model.Performance
|
||||||
import com.example.mobile_labs.common.AppViewModelProvider
|
import com.example.mobile_labs.common.AppViewModelProvider
|
||||||
import com.example.mobile_labs.ui.navigation.Screen
|
import com.example.mobile_labs.ui.navigation.Screen
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
@ -6,12 +6,15 @@
|
|||||||
<string name="performance_author">Автор</string>
|
<string name="performance_author">Автор</string>
|
||||||
<string name="performance_director">Режиссёр</string>
|
<string name="performance_director">Режиссёр</string>
|
||||||
<string name="performance_actors">Актёры</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="about_main_title">О нас</string>
|
||||||
|
<string name="login_main_title">Вход</string>
|
||||||
<string name="performance_view_main_title">Представление</string>
|
<string name="performance_view_main_title">Представление</string>
|
||||||
<string name="people_missing_description">Данные о людях отсутствуют</string>
|
<string name="people_missing_description">Данные о людях отсутствуют</string>
|
||||||
<string name="events_missing_description">Данные о событиях отсутствуют</string>
|
<string name="events_missing_description">Данные о событиях отсутствуют</string>
|
||||||
<string name="performance_missing_description">Данные о представлениях отсутствуют</string>
|
<string name="performance_missing_description">Данные о представлениях отсутствуют</string>
|
||||||
|
<string name="login">Логин</string>
|
||||||
|
<string name="password">Пароль</string>
|
||||||
<string name="about_text">
|
<string name="about_text">
|
||||||
<p>
|
<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!
|
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!
|
||||||
|
Loading…
Reference in New Issue
Block a user