LabWork05

This commit is contained in:
ElEgEv 2023-12-12 17:10:15 +04:00
parent c9fb6f4f33
commit 57ecc27364
45 changed files with 611 additions and 165 deletions

Binary file not shown.

View File

@ -6,6 +6,6 @@ object ApiRoutes {
const val LEVEL = "levels"
const val NATION = "nations"
const val TANK = "tanks"
const val USER_TANK = "userTanks"
const val USER_TANK = "users_tanks"
const val NOT_USER_TANK = "notUserTanks"
}

View File

@ -23,9 +23,9 @@ import ru.ulstu.`is`.pmu.tank.api.model.UserTankCrossRefRemote
interface ServerService {
// :[USER]
@GET(ApiRoutes.USER)
suspend fun getUserByEmail(
@Query("email") email: String
@GET("${ApiRoutes.USER}/{id}")
suspend fun getUser(
@Path("id") userId: Long
): UserRemote
@POST(ApiRoutes.USER)
@ -78,7 +78,7 @@ interface ServerService {
@Query("_limit") limit: Int
): List<NationRemote>
@GET("/getall/${ApiRoutes.NATION}")
@GET(ApiRoutes.NATION)
suspend fun getAllNations(): List<NationRemote>
@GET("${ApiRoutes.NATION}/{id}")
@ -114,14 +114,14 @@ interface ServerService {
@Path("id") id: Long
): TankRemote
@GET("${ApiRoutes.TANK}/forPurchase")
@GET("${ApiRoutes.TANK}?_expand=users_tanks")
suspend fun getTanksForPurchase(
@Path("id") userId: Long
@Query("id") userId: Long
): List<TankRemote>
@GET("${ApiRoutes.TANK}/myHangar")
@GET("${ApiRoutes.TANK}?_embed=users_tanks")
suspend fun getTanksFromHangar(
@Path("id") userId: Long
@Query("id") id: Long
): List<TankWithNationAndLevelRemote>
@POST(ApiRoutes.TANK)
@ -144,10 +144,8 @@ interface ServerService {
// :[USER_TANK_CROSS_REF]
@GET("${ApiRoutes.USER_TANK}?_hangar={userId}")
suspend fun getUserTankCrossRef(
@Path("userId") userId: Long,
): List<UserTankCrossRefRemote>
@GET(ApiRoutes.USER_TANK)
suspend fun getUserTankCrossRef(): List<UserTankCrossRefRemote>
@POST(ApiRoutes.USER_TANK)
suspend fun insertUserTankCrossRef(
@ -162,7 +160,7 @@ interface ServerService {
// ![USER_TANK_CROSS_REF]
companion object {
private const val BASE_URL = "http://10.0.2.2:8079/"
private const val BASE_URL = ApiRoutes.BASE
@Volatile
private var INSTANCE: ServerService? = null

View File

@ -107,5 +107,4 @@ class NationRemoteMediator(
}
}
}
}

View File

@ -0,0 +1,111 @@
package ru.ulstu.`is`.pmu.tank.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import retrofit2.HttpException
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.model.toNation
import ru.ulstu.`is`.pmu.tank.api.model.toTank
import ru.ulstu.`is`.pmu.tank.database.AppDatabase
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.RemoteKeyType
import ru.ulstu.`is`.pmu.tank.model.RemoteKeys
import ru.ulstu.`is`.pmu.tank.repository.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineTankRepository
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class TankRemoteMediator(
private val service: ServerService,
private val dbTankRepository: OfflineTankRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Nation>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Nation>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val tanks = service.getTanks().map { it.toTank() }
val endOfPaginationReached = tanks.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.NATION)
dbTankRepository.deleteAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = tanks.map {
RemoteKeys(
entityId = it.tankId!!.toInt(),
type = RemoteKeyType.NATION,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbTankRepository.insertMany(tanks)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Nation>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { nation ->
dbRemoteKeyRepository.getAllRemoteKeys(nation.uid!!.toInt(), RemoteKeyType.NATION)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Nation>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { nation ->
dbRemoteKeyRepository.getAllRemoteKeys(nation.uid!!.toInt(), RemoteKeyType.NATION)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Nation>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { nationUid ->
dbRemoteKeyRepository.getAllRemoteKeys(nationUid.toInt(), RemoteKeyType.NATION)
}
}
}
}

View File

@ -7,16 +7,16 @@ import ru.ulstu.`is`.pmu.tank.model.Nation
@Serializable
data class LevelRemote (
val uid: Long = 0,
val id: Long = 0,
val level: Int = 0
)
fun LevelRemote.toLevel(): Level = Level(
uid,
uid = id,
level
)
fun Level.toRemote(): LevelRemote = LevelRemote(
uid!!,
id = uid!!,
level
)

View File

@ -5,16 +5,16 @@ import ru.ulstu.`is`.pmu.tank.model.Nation
@Serializable
data class NationRemote(
val uid: Long = 0,
val id: Long = 0,
val nationName: String = ""
)
fun NationRemote.toNation(): Nation = Nation(
uid,
uid = id,
nationName
)
fun Nation.toRemote(): NationRemote = NationRemote(
uid!!,
id = uid!!,
nationName
)

View File

@ -8,31 +8,28 @@ import ru.ulstu.`is`.pmu.tank.model.Tank
@Serializable
data class TankRemote(
val tankId: Long = 0,
val id: Long = 0,
val name: String = "",
val price: Int = 0,
val miniature: String = "",
val imageId: Long = 0,
val levelId: Long = 0,
val nationId: Long = 0,
)
fun TankRemote.toTank(): Tank = Tank(
tankId = tankId,
tankId = id,
name = name,
price = price,
miniature = miniature.toBitmap(),
imageId = imageId,
levelId = levelId,
nationId = nationId
)
fun Tank.toRemote(): TankRemote = TankRemote(
tankId = tankId!!,
id = tankId!!,
name = name,
price = price,
miniature = miniature.toBase64(),
imageId = imageId,
levelId = levelId!!,
nationId = nationId!!
)

View File

@ -7,7 +7,7 @@ import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
@Serializable
data class TankWithNationAndLevelRemote(
val tankId: Long,
val id: Long,
val name: String,
val price: Int,
val image: String,
@ -16,7 +16,7 @@ data class TankWithNationAndLevelRemote(
)
fun TankWithNationAndLevelRemote.toTankWithNationAndLevel(): TankWithNationAndLevel = TankWithNationAndLevel(
tankId = tankId,
tankId = id,
name = name,
price = price,
image = image.toBitmap(),
@ -25,7 +25,7 @@ fun TankWithNationAndLevelRemote.toTankWithNationAndLevel(): TankWithNationAndLe
)
fun TankWithNationAndLevel.toRemote(): TankWithNationAndLevelRemote = TankWithNationAndLevelRemote(
tankId = tankId!!,
id = tankId!!,
name = name,
price = price,
image = image.toBase64(),

View File

@ -9,8 +9,8 @@ data class UserRemote (
val id: Long = 0,
val nickname: String = "",
val email: String = "",
val role: Int = -1,
val password: String = "",
val role: Int = 0,
val balance: Int = 0
)

View File

@ -1,9 +1,21 @@
package ru.ulstu.`is`.pmu.tank.api.model
import kotlinx.serialization.Serializable
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
@Serializable
data class UserTankCrossRefRemote (
val userId: Long = 0,
val id: Long = 0,
val tankId: Long = 0
)
fun UserTankCrossRefRemote.toUserTankCrossRef(): UserTankCrossRef = UserTankCrossRef(
userId = id,
tankId = tankId
)
fun UserTankCrossRef.toRemote(): UserTankCrossRefRemote = UserTankCrossRefRemote(
id = userId,
tankId = tankId
)

View File

@ -35,7 +35,7 @@ class RestNationRepository(
return nations
}
override suspend fun getAll(): Flow<PagingData<Nation>> {
override fun getAll(): Flow<PagingData<Nation>> {
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(

View File

@ -1,42 +1,110 @@
package ru.ulstu.`is`.pmu.tank.api.repository
import android.graphics.Bitmap
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import ru.ulstu.`is`.pmu.tank.api.ServerService
import ru.ulstu.`is`.pmu.tank.api.model.UserTankCrossRefRemote
import ru.ulstu.`is`.pmu.tank.api.model.toLevel
import ru.ulstu.`is`.pmu.tank.api.model.toNation
import ru.ulstu.`is`.pmu.tank.api.model.toRemote
import ru.ulstu.`is`.pmu.tank.api.model.toTank
import ru.ulstu.`is`.pmu.tank.api.model.toTankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.api.model.toUserTankCrossRef
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.OfflineLevelRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineTankRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineUsersTanksRepository
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
import java.nio.file.Files.find
class RestTankRepository (
private val service: ServerService,
private val dbTankRepository: OfflineTankRepository,
private val dbUsersTanksRepository: OfflineUsersTanksRepository
) : TankRepository {
override suspend fun getAll(): List<Tank> {
dbTankRepository.deleteAll()
val tanks = service.getTanks().map { it.toTank() }
dbTankRepository.insertMany(tanks)
return tanks
return service.getTanks().map { it.toTank() }
}
override suspend fun getForUserAll(userId: Long): List<Tank> {
return service.getTanksForPurchase(userId).map { it.toTank() }
val totalList: List<Tank> = getAll()
val totalUserTankList: List<UserTankCrossRef> = service.getUserTankCrossRef().map{ it.toUserTankCrossRef() }
//спискок, который вернём
var supportList = ArrayList<Tank>()
//вспомогательный список для проверки отсутствия танка у пользователя
var notToUser: List<UserTankCrossRef> = totalList.map{ UserTankCrossRef(userId, it.tankId!!) }
if (totalUserTankList.isEmpty()){
return totalList
} else {
notToUser.forEach(){userTank ->
if(!totalUserTankList.contains(userTank)){
supportList.add(totalList.firstOrNull { it.tankId == userTank.tankId }!!)
}
}
return supportList
}
}
//очень классная штука
private suspend fun <T> Flow<List<T>>.flattenToList() =
flatMapConcat { it.asFlow() }.toList()
override suspend fun getTank(uid: Long): Tank {
return service.getTank(uid).toTank()
}
override suspend fun getUserTanks(uid: Long): List<TankWithNationAndLevel> {
return service.getTanksFromHangar(uid).map { it.toTankWithNationAndLevel() }
override suspend fun getUserTanks(userId: Long): List<TankWithNationAndLevel> {
val totalList: List<Tank> = getAll()
val totalLevelList: List<Level> = service.getLevels().map { it.toLevel() }
val totalNationList: List<Nation> = service.getAllNations().map { it.toNation() }
//все имеющиеся танки у пользователя
val totalUserTankList: List<UserTankCrossRef> = service.getUserTankCrossRef().map{ it.toUserTankCrossRef() }
//спискок, который вернём
var supportList = ArrayList<TankWithNationAndLevel>()
//вспомогательный список для проверки наличия танка у пользователя
var notToUser: List<UserTankCrossRef> = totalList.map{ UserTankCrossRef(userId, it.tankId!!) }
if (totalUserTankList.isEmpty()){
return listOf()
} else {
notToUser.forEach(){userTank ->
if(totalUserTankList.contains(userTank)){
val tank = totalList.firstOrNull { it.tankId == userTank.tankId }!!
supportList.add(
TankWithNationAndLevel(tank.tankId, tank.name, tank.price, tank.miniature,
totalLevelList.first { it.uid == tank.levelId }.level,
totalNationList.first { it.uid == tank.nationId }.nationName)
)
}
}
return supportList
}
}
override suspend fun insertTank(tank: Tank, image: Bitmap) {
service.insertTank(tank.toRemote())
}
override suspend fun buyTank(tankId: Long, userId: Long) {
service.insertUserTankCrossRef(UserTankCrossRefRemote(userId, tankId))
}
override suspend fun insertMany(tankList: List<Tank>) {
TODO("Not yet implemented")
}
@ -46,7 +114,7 @@ class RestTankRepository (
}
override suspend fun deleteTank(tank: Tank) {
TODO("Not yet implemented")
service.deleteTank(tank.tankId!!)
}
override suspend fun delete(tankId: Long) {

View File

@ -22,8 +22,8 @@ class RestUserRepository(
TODO("Not yet implemented")
}
override suspend fun getSimpleUser(email: String): User {
return service.getUserByEmail(email).toUser()
override suspend fun getSimpleUser(userId: Long): User {
return service.getUser(userId).toUser()
}
override fun getFullUser(uid: Long): Flow<Map<User, List<TankWithNationAndLevel>>> {

View File

@ -31,11 +31,13 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
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
@ -52,10 +54,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.R
import ru.ulstu.`is`.pmu.composeui.navigation.Screen
import ru.ulstu.`is`.pmu.tank.composeui.edit.NationDropDownViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.NationsListUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.TankEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.UsersTanksEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.list.TankListUiState
import ru.ulstu.`is`.pmu.tank.composeui.list.TankListViewModel
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.Tank
@ -70,14 +76,20 @@ import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme
@Composable
fun TankList(
navController: NavController?,
userTankEditViewModel: UsersTanksEditViewModel = viewModel(factory = AppViewModelProvider.Factory),
viewModel: TankListViewModel = viewModel(factory = AppViewModelProvider.Factory),
listNations: NationDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
LaunchedEffect(Unit) {
viewModel.refreshTankListState()
}
// Lazy Column, Pass the numbers array
if (navController != null) {
TankList(
nations = listNations.nationsListUiState,
listTanks = viewModel.tankListUiState.tankList
listTanks = viewModel.tankListUiState.tankList,
viewModel = userTankEditViewModel
) { uid: Long ->
val route = Screen.Constructor.route.replace("{id}", uid.toString())
navController.navigate(route)
@ -89,8 +101,20 @@ fun TankList(
fun ColumnItem(
nation: Nation,
tanks: List<Tank>?,
onClick: (uid: Long) -> Unit
onClick: (uid: Long) -> Unit,
viewModel: UsersTanksEditViewModel
) {
val scope = rememberCoroutineScope()
val tank = viewModel.usersTanksUiState
fun handleTankButtonClick() {
scope.launch {
if (tank.userTankCrossRef.tankId == 0L) {
viewModel.saveUserTank()
}
}
}
Column(
modifier = Modifier.padding(0.dp, 10.dp)
) {
@ -121,6 +145,7 @@ fun ColumnItem(
{
if(!tanks.isNullOrEmpty()){
tanks.forEach { tank ->
//TODO
if(tank.tankId != null){
key(tank.tankId) {
//val studentId = Screen.StudentView.route.replace("{id}", index.toString())
@ -152,7 +177,7 @@ fun ColumnItem(
colors = ButtonDefaults.buttonColors(
containerColor = CustomRed,
contentColor = CustomDark),
onClick = { onClick(tank.tankId) }
onClick = { handleTankButtonClick() }
) {
//navController?.navigate(Screen.Hangar.route)
//navController?.navigate(studentId)
@ -189,6 +214,7 @@ fun ColumnItem(
private fun TankList(
nations: NationsListUiState,
listTanks: List<Tank>,
viewModel: UsersTanksEditViewModel,
onClick: (uid: Long) -> Unit,
) {
LazyColumn(
@ -231,7 +257,7 @@ private fun TankList(
}
items(nations.nationList) {nation ->
ColumnItem(nation = nation, tanks = totalDictionary[nation], onClick = onClick)
ColumnItem(nation = nation, tanks = totalDictionary[nation], onClick = onClick, viewModel = viewModel)
}
// items(list/array) places number of
@ -254,6 +280,7 @@ fun TankListPreview() {
) {
TankList(
nations = NationsListUiState(listOf()),
viewModel = viewModel(factory = AppViewModelProvider.Factory),
listTanks = (1..20).map { i -> Tank.getTank(i.toLong()) }
) { }
}
@ -270,6 +297,7 @@ fun TankEmptyListPreview() {
) {
TankList(
nations = NationsListUiState(listOf()),
viewModel = viewModel(factory = AppViewModelProvider.Factory),
listTanks = listOf()
) { }
}

View File

@ -74,7 +74,6 @@ class TankEditViewModel(
&& price > 0
&& levelId!! != 0L
&& nationId!! != 0L
&& imageId !! != 0L
&& miniature != null
}
}
@ -101,7 +100,6 @@ fun TankDetails.toTank(uid: Long = 0): Tank = Tank(
levelId = levelId,
nationId = nationId,
miniature = miniature,
imageId = imageId
)
fun Tank.toDetails(): TankDetails = TankDetails(
@ -110,7 +108,6 @@ fun Tank.toDetails(): TankDetails = TankDetails(
levelId = levelId,
nationId = nationId,
miniature = miniature,
imageId = imageId
)
fun Tank.toUiState(isEntryValid: Boolean = false): TankUiState = TankUiState(

View File

@ -23,12 +23,12 @@ class UserEditViewModel(
//private val userUid: Long = checkNotNull(savedStateHandle["id"])
private val userEmail: String = "egor@mail.ru"
private val userId: Long = 100L
init {
viewModelScope.launch {
if (userEmail.length > 0) {
userUiState = userRepository.getSimpleUser(userEmail)
if (userId > 0) {
userUiState = userRepository.getSimpleUser(userId)
.toUiState(true)
}
}
@ -43,10 +43,11 @@ class UserEditViewModel(
suspend fun saveUser() {
if (validateInput()) {
if (userEmail.length > 0) {
userRepository.updateUser(userUiState.userDetails.toUser(100L))
val user: User = userUiState.userDetails.toUser()
if (user.userId != 0.toLong()) {
userRepository.updateUser(user)
} else {
userRepository.insertUser(userUiState.userDetails.toUser())
userRepository.insertUser(user)
}
}
}

View File

@ -0,0 +1,36 @@
package ru.ulstu.`is`.pmu.tank.composeui.edit
import android.util.Log
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 kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.model.UserRole
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.OfflineUsersTanksRepository
import ru.ulstu.`is`.pmu.tank.repository.UserRepository
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
class UsersTanksEditViewModel(
savedStateHandle: SavedStateHandle,
private val usersTanksRepository: UsersTanksRepository
) : ViewModel() {
var usersTanksUiState by mutableStateOf(UserTankCrossRefUiState())
private set
//val tankId: Long = checkNotNull(savedStateHandle["id"])
private val userId: Long = 100L
suspend fun saveUserTank() {
//usersTanksRepository.insertUserTank(UserTankCrossRef(userId, tankId))
}
}
data class UserTankCrossRefUiState(
val userTankCrossRef: UserTankCrossRef = UserTankCrossRef.getEmpty(),
)

View File

@ -1,21 +1,31 @@
package ru.ulstu.`is`.pmu.tank.composeui.list
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.repository.NationRepository
class NationListViewModel(
nationRepository: NationRepository
private val nationRepository: NationRepository
) : ViewModel() {
private val pager = Pager(
config = PagingConfig(pageSize = 10),
pagingSourceFactory = {
nationRepository.pagingSource()
}
)
var nationPagingFlow by mutableStateOf(NationPagingFlowState())
private set
val nationPagingFlow = pager.flow.cachedIn(viewModelScope)
fun refresh() {
val pagingSource = nationRepository.getAll()
nationPagingFlow = NationPagingFlowState(pagingSource.cachedIn(viewModelScope))
}
}
data class NationPagingFlowState(
val flow: Flow<PagingData<Nation>> = emptyFlow(),
)

View File

@ -5,17 +5,22 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import ru.ulstu.`is`.pmu.tank.database.AppDataContainer
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
class TankListViewModel(
private val tankRepository: TankRepository,
private val usersTanksRepository: UsersTanksRepository,
private var userId: Long = 100L
) : ViewModel() {
var tankListUiState by mutableStateOf(TankListUiState())
@ -31,6 +36,18 @@ class TankListViewModel(
fun setUserId(uid: Long){
userId = uid
}
fun refreshTankListState() {
viewModelScope.launch {
tankListUiState = TankListUiState(tankRepository.getForUserAll(userId))
}
}
suspend fun refreshUserTanksListState(){
viewModelScope.launch {
usersTanksUiState = UserTankListUiState(tankRepository.getUserTanks(userId))
}
}
}
data class TankListUiState(val tankList: List<Tank> = listOf())

View File

@ -19,16 +19,14 @@ interface TankDao {
fun getAll(): List<Tank>
//получить конкретный танк
@Query("select t.*, ti.data from tanks AS t " +
"LEFT JOIN tank_images as ti on t.image_id = ti.image_id " +
@Query("select t.* from tanks AS t " +
"where t.tankId = :uid")
fun getTankUid(uid: Long): Tank
//получаем все танки пользователя по его Id
@Query(
"SELECT t.tankId, t.name, t.price, t.image_id, l.level, n.nationName, ti.data AS image FROM users_tanks AS ut " +
"SELECT t.tankId, t.name, t.price, t.miniature AS image, l.level, n.nationName FROM users_tanks AS ut " +
"LEFT JOIN tanks as t on ut.tankId = t.tankId " +
"LEFT JOIN tank_images as ti on t.image_id = ti.image_id " +
"LEFT JOIN levels as l on t.levelId = l.uid " +
"LEFT JOIN nations as n on t.nationId = n.uid " +
"WHERE ut.userId = :uid GROUP BY t.nationId, t.levelId ORDER BY t.nationId"

View File

@ -29,8 +29,8 @@ interface UserDao {
)
fun getUserUid(uid: Long): Flow<Map<User, List<TankWithNationAndLevel>>>
@Query("select * from users where users.email = :email")
fun getSimpleUserUid(email: String): User
@Query("select * from users where users.userId = :userId")
fun getSimpleUserUid(userId: Long): User
//добавить танк в ангар пользователя
@Insert

View File

@ -0,0 +1,25 @@
package ru.ulstu.`is`.pmu.tank.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
@Dao
interface UsersTanksDao {
@Query("select * from users_tanks GROUP BY userId")
fun getAll(): List<UserTankCrossRef>
@Query("select * from users_tanks WHERE userId = :userId GROUP BY userId")
suspend fun getAllById(userId: Long): List<UserTankCrossRef>
@Insert
suspend fun insert(userTankCrossRef: UserTankCrossRef)
@Insert
suspend fun insertMany(userTankCrossRefList: List<UserTankCrossRef>)
@Query("DELETE FROM users_tanks")
suspend fun deleteAll()
}

View File

@ -13,14 +13,17 @@ import ru.ulstu.`is`.pmu.tank.repository.OfflineNationRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineRemoteKeyRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineTankRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineUserRepository
import ru.ulstu.`is`.pmu.tank.repository.OfflineUsersTanksRepository
import ru.ulstu.`is`.pmu.tank.repository.TankRepository
import ru.ulstu.`is`.pmu.tank.repository.UserRepository
import ru.ulstu.`is`.pmu.tank.repository.UsersTanksRepository
interface AppContainer {
val levelRestRepository: RestLevelRepository
val nationRestRepository: RestNationRepository
val userRestRepository: RestUserRepository
val tankRestRepository: RestTankRepository
val usersTanksRepository: UsersTanksRepository
companion object {
const val TIMEOUT = 5000L
@ -36,18 +39,18 @@ class AppDataContainer(private val context: Context) : AppContainer {
OfflineNationRepository(AppDatabase.getInstance(context).nationDao())
}
private val tankRepository: OfflineTankRepository by lazy {
OfflineTankRepository(
AppDatabase.getInstance(context).tankDao(),
tankImageDao = AppDatabase.getInstance(context).tankImageDao())
OfflineTankRepository(AppDatabase.getInstance(context).tankDao(),
AppDatabase.getInstance(context).usersTanksDao())
}
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
}
override val usersTanksRepository: OfflineUsersTanksRepository by lazy {
OfflineUsersTanksRepository(AppDatabase.getInstance(context).usersTanksDao())
}
override val levelRestRepository: RestLevelRepository by lazy {
RestLevelRepository(
service = ServerService.getInstance(),
@ -74,7 +77,8 @@ class AppDataContainer(private val context: Context) : AppContainer {
override val tankRestRepository: RestTankRepository by lazy {
RestTankRepository(
service = ServerService.getInstance(),
dbTankRepository = tankRepository
dbTankRepository = tankRepository,
dbUsersTanksRepository = usersTanksRepository
)
}
}

View File

@ -16,6 +16,7 @@ import ru.ulstu.`is`.pmu.tank.dao.RemoteKeysDao
import ru.ulstu.`is`.pmu.tank.dao.TankDao
import ru.ulstu.`is`.pmu.tank.dao.TankImageDao
import ru.ulstu.`is`.pmu.tank.dao.UserDao
import ru.ulstu.`is`.pmu.tank.dao.UsersTanksDao
import ru.ulstu.`is`.pmu.tank.model.Level
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tank.model.RemoteKeys
@ -46,10 +47,11 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun tankDao() : TankDao
abstract fun userDao() : UserDao
abstract fun tankImageDao() : TankImageDao
abstract fun usersTanksDao(): UsersTanksDao
abstract fun remoteKeysDao(): RemoteKeysDao
companion object {
private const val DB_NAME: String = "21-db"
private const val DB_NAME: String = "22-db"
@Volatile
private var INSTANCE: AppDatabase? = null
@ -118,17 +120,17 @@ abstract class AppDatabase : RoomDatabase() {
//Tanks
val tankDao = database.tankDao()
val tank1 = Tank(20L,"МС-1", 1000, PrepopulateStore.getProductMiniature(context, 1L), 1L, level1.uid, nation1.uid)
val tank2 = Tank(21L, "Т-34-85", 960000, PrepopulateStore.getProductMiniature(context, 2L), 2L, level6.uid, nation1.uid)
val tank10 = Tank(22L, "Pershing", 1260000, PrepopulateStore.getProductMiniature(context, 9L), 9L, level8.uid, nation3.uid)
val tank6 = Tank(23L, "Ferdinand", 2500000, PrepopulateStore.getProductMiniature(context, 8L), 8L, level8.uid, nation2.uid)
val tank3 = Tank(24L, "ИС-2", 1230000, PrepopulateStore.getProductMiniature(context, 3L), 3L, level7.uid, nation1.uid)
val tank4 = Tank(25L, "ИСУ-152", 2350000, PrepopulateStore.getProductMiniature(context, 4L), 4L, level8.uid, nation1.uid)
val tank5 = Tank(26L, "Tiger 1", 1430000,PrepopulateStore.getProductMiniature(context, 5L), 5L, level7.uid, nation2.uid)
val tank7 = Tank(27L, "Tiger 2", 2500000, PrepopulateStore.getProductMiniature(context, 6L), 6L, level8.uid, nation2.uid)
val tank8 = Tank(28L, "Panther", 1350000, PrepopulateStore.getProductMiniature(context, 7L), 7L, level7.uid, nation2.uid)
val tank9 = Tank(29L, "M4A2E3", 990000, PrepopulateStore.getProductMiniature(context, 10L), 10L, level6.uid, nation3.uid)
val tank11 = Tank(30L, "Hellcat", 940000, PrepopulateStore.getProductMiniature(context, 11L), 11L, level7.uid, nation3.uid)
val tank1 = Tank(20L,"МС-1", 1000, PrepopulateStore.getProductMiniature(context, 1L), level1.uid, nation1.uid)
val tank2 = Tank(21L, "Т-34-85", 960000, PrepopulateStore.getProductMiniature(context, 2L), level6.uid, nation1.uid)
val tank10 = Tank(22L, "Pershing", 1260000, PrepopulateStore.getProductMiniature(context, 9L), level8.uid, nation3.uid)
val tank6 = Tank(23L, "Ferdinand", 2500000, PrepopulateStore.getProductMiniature(context, 8L), level8.uid, nation2.uid)
val tank3 = Tank(24L, "ИС-2", 1230000, PrepopulateStore.getProductMiniature(context, 3L), level7.uid, nation1.uid)
val tank4 = Tank(25L, "ИСУ-152", 2350000, PrepopulateStore.getProductMiniature(context, 4L), level8.uid, nation1.uid)
val tank5 = Tank(26L, "Tiger 1", 1430000,PrepopulateStore.getProductMiniature(context, 5L), level7.uid, nation2.uid)
val tank7 = Tank(27L, "Tiger 2", 2500000, PrepopulateStore.getProductMiniature(context, 6L), level8.uid, nation2.uid)
val tank8 = Tank(28L, "Panther", 1350000, PrepopulateStore.getProductMiniature(context, 7L), level7.uid, nation2.uid)
val tank9 = Tank(29L, "M4A2E3", 990000, PrepopulateStore.getProductMiniature(context, 10L), level6.uid, nation3.uid)
val tank11 = Tank(30L, "Hellcat", 940000, PrepopulateStore.getProductMiniature(context, 11L), level7.uid, nation3.uid)
tankDao.insert(tank1)
tankDao.insert(tank2)

View File

@ -13,15 +13,6 @@ import ru.ulstu.`is`.pmu.tank.database.Converters
@Entity(
tableName = "tanks",
foreignKeys = [
ForeignKey(
entity = TankImage::class,
parentColumns = ["image_id"],
childColumns = ["image_id"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.RESTRICT
)
]
)
data class Tank(
@PrimaryKey(autoGenerate = true)
@ -31,8 +22,6 @@ data class Tank(
@ColumnInfo(name = "price")
val price: Int,
val miniature: Bitmap,
@ColumnInfo(name = "image_id", index = true)
val imageId: Long,
@ColumnInfo(name = "levelId", index = true)
val levelId: Long?,
@ColumnInfo(name = "nationId", index = true)
@ -43,10 +32,9 @@ data class Tank(
name: String,
price: Int,
miniature: Bitmap,
imageId: Long,
level: Level,
nation: Nation
) : this(null, name, price, miniature, imageId, level.uid, nation.uid)
) : this(null, name, price, miniature, level.uid, nation.uid)
companion object {
fun getTank(index: Long = 0): Tank {
@ -55,7 +43,6 @@ data class Tank(
"Первый танк",
100000,
miniature = getEmptyBitmap(),
1L,
1,
1
)
@ -72,7 +59,6 @@ data class Tank(
if (name != other.name) return false
if (price != other.price) return false
if (miniature != other.miniature) return false
if (imageId != other.imageId) return false
if (levelId != other.levelId) return false
if (nationId != other.nationId) return false

View File

@ -15,4 +15,13 @@ import org.jetbrains.annotations.NotNull
data class UserTankCrossRef(
val userId: Long,
val tankId: Long
){
companion object {
fun getEmpty(): UserTankCrossRef {
return UserTankCrossRef(
userId = 0,
tankId = 0
)
}
}
}

View File

@ -9,7 +9,7 @@ import ru.ulstu.`is`.pmu.tank.model.NationWithTanks
interface NationRepository {
suspend fun getAllNations(): List<Nation>
suspend fun getAll(): Flow<PagingData<Nation>>
fun getAll(): Flow<PagingData<Nation>>
fun getSimpleNation(uid: Long): Flow<Nation?>
fun getFullNation(uid: Long): Flow<NationWithTanks?>
fun pagingSource(): PagingSource<Int, Nation>

View File

@ -9,7 +9,7 @@ import ru.ulstu.`is`.pmu.tank.model.NationWithTanks
class OfflineNationRepository(private val nationDao: NationDao) : NationRepository {
override suspend fun getAllNations(): List<Nation> = nationDao.getAll()
override suspend fun getAll(): Flow<PagingData<Nation>> {
override fun getAll(): Flow<PagingData<Nation>> {
TODO("Not yet implemented")
}

View File

@ -4,14 +4,16 @@ import android.graphics.Bitmap
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.dao.TankDao
import ru.ulstu.`is`.pmu.tank.dao.TankImageDao
import ru.ulstu.`is`.pmu.tank.dao.UsersTanksDao
import ru.ulstu.`is`.pmu.tank.model.Tank
import ru.ulstu.`is`.pmu.tank.model.TankExtra
import ru.ulstu.`is`.pmu.tank.model.TankImage
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
class OfflineTankRepository(
private val tankDao: TankDao,
private val tankImageDao: TankImageDao
private val usersTanksDao: UsersTanksDao
) : TankRepository {
override suspend fun getAll(): List<Tank> = tankDao.getAll()
@ -22,14 +24,11 @@ class OfflineTankRepository(
override suspend fun getUserTanks(uid: Long): List<TankWithNationAndLevel> = tankDao.getUserTanks(uid)
override suspend fun insertTank(tank: Tank, image: Bitmap) {
val imageId = tankImageDao.insert(
TankImage(
id = null,
data = image
)
)
tankDao.insert(tank)
}
tankDao.insert(tank.copy(imageId = imageId))
override suspend fun buyTank(tankId: Long, userId: Long) {
usersTanksDao.insert(UserTankCrossRef(userId, tankId))
}
override suspend fun insertMany(tankList: List<Tank>) {
@ -37,15 +36,7 @@ class OfflineTankRepository(
}
override suspend fun updateTank(tank: Tank, image: Bitmap) {
val imageId = tankImageDao.insert(
TankImage(
id = null,
data = image
)
)
tankDao.update(tank.copy(imageId = imageId))
tankImageDao.delete(tank.imageId)
tankDao.update(tank.copy())
}
override suspend fun deleteTank(tank: Tank) = tankDao.delete(tank)

View File

@ -8,7 +8,7 @@ import ru.ulstu.`is`.pmu.tank.model.User
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAllUsers(): Flow<List<User>> = userDao.getAll()
override suspend fun getSimpleUser(email: String): User = userDao.getSimpleUserUid(email)
override suspend fun getSimpleUser(userId: Long): User = userDao.getSimpleUserUid(userId)
override fun getFullUser(uid: Long): Flow<Map<User, List<TankWithNationAndLevel>>> = userDao.getUserUid(uid)

View File

@ -0,0 +1,29 @@
package ru.ulstu.`is`.pmu.tank.repository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.dao.UsersTanksDao
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
class OfflineUsersTanksRepository(
private val usersTanksDao: UsersTanksDao
) : UsersTanksRepository {
override fun getAll(): List<UserTankCrossRef> {
return usersTanksDao.getAll()
}
override suspend fun getAllById(userId: Long): List<UserTankCrossRef> {
return usersTanksDao.getAllById(userId)
}
override suspend fun insertUserTank(userTankCrossRef: UserTankCrossRef) {
return usersTanksDao.insert(userTankCrossRef)
}
override suspend fun insertListUserTank(userTankCrossRefList: List<UserTankCrossRef>) {
return usersTanksDao.insertMany(userTankCrossRefList)
}
override suspend fun deleteUserTanks(userTankCrossRef: UserTankCrossRef) {
return usersTanksDao.deleteAll()
}
}

View File

@ -12,6 +12,7 @@ interface TankRepository {
suspend fun getTank(uid: Long): Tank
suspend fun getUserTanks(uid: Long): List<TankWithNationAndLevel>
suspend fun insertTank(tank: Tank, image: Bitmap)
suspend fun buyTank(tankId: Long, userId: Long)
suspend fun insertMany(tankList: List<Tank>)
suspend fun updateTank(tank: Tank, image: Bitmap)
suspend fun deleteTank(tank: Tank)

View File

@ -6,7 +6,7 @@ import ru.ulstu.`is`.pmu.tank.model.User
interface UserRepository {
fun getAllUsers(): Flow<List<User>>
suspend fun getSimpleUser(email: String): User
suspend fun getSimpleUser(userId: Long): User
fun getFullUser(uid: Long): Flow<Map<User, List<TankWithNationAndLevel>>>
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)

View File

@ -0,0 +1,14 @@
package ru.ulstu.`is`.pmu.tank.repository
import kotlinx.coroutines.flow.Flow
import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel
import ru.ulstu.`is`.pmu.tank.model.User
import ru.ulstu.`is`.pmu.tank.model.UserTankCrossRef
interface UsersTanksRepository {
fun getAll(): List<UserTankCrossRef>
suspend fun getAllById(userId: Long): List<UserTankCrossRef>
suspend fun insertUserTank(userTankCrossRef: UserTankCrossRef)
suspend fun insertListUserTank(userTankCrossRefList: List<UserTankCrossRef>)
suspend fun deleteUserTanks(userTankCrossRef: UserTankCrossRef)
}

View File

@ -17,6 +17,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@ -46,6 +47,10 @@ fun Hangar(
){
viewModel.setUserId(100L)
LaunchedEffect(Unit) {
viewModel.refreshUserTanksListState()
}
//новый вызов основного списка
Hangar(tankList = viewModel.usersTanksUiState.userTankList )
}
@ -82,7 +87,7 @@ private fun Hangar(
) {
var supportSizeRow = 1
if(n == supportCountRow && supportSizeRow % 2 != 0){
if(n == supportCountRow && supportSizeRow % 2 != 0 && tankList.size != 2){
supportSizeRow = 0
}

View File

@ -39,6 +39,7 @@ import ru.ulstu.`is`.pmu.tank.composeui.list.NationListViewModel
import ru.ulstu.`is`.pmu.tank.model.Nation
import ru.ulstu.`is`.pmu.tanks.composeui.image.Dimensions
import ru.ulstu.`is`.pmu.ui.AppViewModelProvider
import androidx.compose.runtime.LaunchedEffect as LaunchedEffect
@OptIn(ExperimentalFoundationApi::class)
@Composable
@ -46,13 +47,17 @@ fun NationList(
navController: NavController,
viewModel: NationListViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val nations = viewModel.nationPagingFlow.collectAsLazyPagingItems()
val nations = viewModel.nationPagingFlow.flow.collectAsLazyPagingItems()
fun handleAddButtonClick() {
val route = Screen.getEntityRoute(Screen.Constructor, 0)
navController.navigate(route)
}
LaunchedEffect(Unit) {
viewModel.refresh()
}
Box(
modifier = Modifier
.fillMaxSize()

View File

@ -11,13 +11,17 @@ import ru.ulstu.`is`.pmu.tank.composeui.edit.NationDropDownViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.NationsListUiState
import ru.ulstu.`is`.pmu.tank.composeui.edit.TankEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.UserEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.edit.UsersTanksEditViewModel
import ru.ulstu.`is`.pmu.tank.composeui.list.NationListViewModel
import ru.ulstu.`is`.pmu.tank.composeui.list.TankListViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
TankListViewModel(tankApplication().container.tankRestRepository)
TankListViewModel(
tankApplication().container.tankRestRepository,
tankApplication().container.usersTanksRepository
)
}
initializer {
TankEditViewModel(
@ -31,6 +35,12 @@ object AppViewModelProvider {
tankApplication().container.userRestRepository
)
}
initializer {
UsersTanksEditViewModel(
this.createSavedStateHandle(),
tankApplication().container.usersTanksRepository
)
}
initializer {
LevelDropDownViewModel(tankApplication().container.levelRestRepository)
}

File diff suppressed because one or more lines are too long