final variant
This commit is contained in:
parent
0d0ffbeb2f
commit
f0ce6018a9
@ -53,6 +53,8 @@ kotlin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.compose.runtime:runtime-livedata")
|
||||
|
||||
//Core
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||
|
@ -12,22 +12,29 @@ import retrofit2.http.Query
|
||||
import com.example.myapplication.api.models.UserRemote
|
||||
import com.example.myapplication.api.models.ChallengeRemote
|
||||
import com.example.myapplication.api.models.CategoryRemote
|
||||
import android.util.Log
|
||||
import com.example.myapplication.api.models.Credentials
|
||||
import com.example.myapplication.api.models.Token
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface MyServerService {
|
||||
@GET("users")
|
||||
suspend fun getUsers(
|
||||
@Query("_page") page: Int,
|
||||
@Query("page") page: Int,
|
||||
@Query("_limit") limit: Int,
|
||||
): List<UserRemote>
|
||||
|
||||
@GET("challenges")
|
||||
suspend fun getChallenges(
|
||||
@Query("_page") page: Int,
|
||||
@Query("page") page: Int,
|
||||
@Query("_limit") limit: Int,
|
||||
): List<ChallengeRemote>
|
||||
|
||||
@GET("categories")
|
||||
suspend fun getCategories(
|
||||
@Query("_page") page: Int,
|
||||
@Query("page") page: Int,
|
||||
@Query("_limit") limit: Int,
|
||||
): List<CategoryRemote>
|
||||
|
||||
@ -41,8 +48,27 @@ interface MyServerService {
|
||||
@Path("id") id: Int,
|
||||
): ChallengeRemote
|
||||
|
||||
@POST("login")
|
||||
suspend fun login(
|
||||
@Body credentials: Credentials
|
||||
): Token
|
||||
|
||||
@POST("challenges")
|
||||
suspend fun createChallenge(
|
||||
@Body challenge: ChallengeRemote,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val BASE_URL = "http://10.0.2.2:8000/api/"
|
||||
private var _token: String = ""
|
||||
|
||||
fun getToken(): String {
|
||||
return _token
|
||||
}
|
||||
|
||||
fun setToken(token: String) {
|
||||
_token = token
|
||||
}
|
||||
|
||||
@Volatile
|
||||
private var INSTANCE: MyServerService? = null
|
||||
@ -52,7 +78,26 @@ interface MyServerService {
|
||||
val logger = HttpLoggingInterceptor()
|
||||
logger.level = HttpLoggingInterceptor.Level.BASIC
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectTimeout(1, TimeUnit.DAYS)
|
||||
.readTimeout(1, TimeUnit.DAYS)
|
||||
.writeTimeout(1, TimeUnit.DAYS)
|
||||
.retryOnConnectionFailure(false)
|
||||
.callTimeout(1, TimeUnit.DAYS)
|
||||
.addInterceptor(logger)
|
||||
.addInterceptor {
|
||||
val originalRequest = it.request()
|
||||
if (_token.isEmpty()) {
|
||||
it.proceed(originalRequest)
|
||||
} else {
|
||||
it.proceed(
|
||||
originalRequest
|
||||
.newBuilder()
|
||||
.header("Authorization", "Bearer $_token")
|
||||
.method(originalRequest.method, originalRequest.body)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
|
@ -70,8 +70,7 @@ import java.io.IOException
|
||||
nextKey = nextKey
|
||||
)
|
||||
}
|
||||
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CATEGORY)
|
||||
dbCategoryRepository.clearCategories()
|
||||
|
||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||
dbCategoryRepository.insertCategories(categories)
|
||||
}
|
||||
|
@ -77,8 +77,6 @@ class ChallengeRemoteMediator(
|
||||
)
|
||||
}
|
||||
|
||||
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CHALLENGE)
|
||||
dbChallengeRepository.clearChallenges()
|
||||
dbUserRepository.getAll()
|
||||
dbCategoryRepository.getAll()
|
||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||
|
@ -5,6 +5,7 @@ import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import com.example.myapplication.api.MyServerService
|
||||
import com.example.myapplication.api.models.toChallengeRemote
|
||||
import com.example.myapplication.api.models.toUserWithChallenges
|
||||
import com.example.myapplication.api.users.UserRemoteMediator
|
||||
import com.example.myapplication.common.AppDataContainer
|
||||
@ -49,4 +50,8 @@ class RestChallengeRepository(
|
||||
pagingSourceFactory = pagingSourceFactory
|
||||
).flow
|
||||
}
|
||||
|
||||
override suspend fun insert(challenge: Challenge) {
|
||||
service.createChallenge(challenge.toChallengeRemote())
|
||||
}
|
||||
}
|
@ -19,4 +19,12 @@ fun ChallengeRemote.toChallenge(): Challenge = Challenge(
|
||||
challenge_status,
|
||||
category_id,
|
||||
user_id
|
||||
)
|
||||
|
||||
fun Challenge.toChallengeRemote(): ChallengeRemote = ChallengeRemote(
|
||||
challenge_id!!,
|
||||
challenge_name,
|
||||
challenge_status,
|
||||
category_id!!,
|
||||
user_id!!,
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package com.example.myapplication.api.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Credentials(
|
||||
val email: String = "",
|
||||
val password: String = "",
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package com.example.myapplication.api.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Token(
|
||||
val token: String
|
||||
)
|
@ -74,8 +74,7 @@ class UserRemoteMediator (
|
||||
nextKey = nextKey
|
||||
)
|
||||
}
|
||||
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.USER)
|
||||
dbUserRepository.clearUsers()
|
||||
|
||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||
dbChallengeRepository.getAll()
|
||||
dbUserRepository.insertUsers(users)
|
||||
|
@ -7,8 +7,11 @@ import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.example.myapplication.ChallengeApplication
|
||||
import com.example.myapplication.ui.category.list.CategoryListViewModel
|
||||
import com.example.myapplication.ui.challenge.view.AdminChallengeViewModel
|
||||
import com.example.myapplication.ui.user.list.UserListViewModel
|
||||
import com.example.myapplication.ui.user.view.UserViewModel
|
||||
import com.example.myapplication.ui.login.LoginViewModel
|
||||
import com.example.myapplication.ui.user.view.AdminUserViewModel
|
||||
|
||||
object AppViewModelProvider {
|
||||
val Factory = viewModelFactory {
|
||||
@ -21,6 +24,15 @@ object AppViewModelProvider {
|
||||
initializer {
|
||||
UserViewModel(this.createSavedStateHandle(), challengeApplication().container.userRestRepository)
|
||||
}
|
||||
initializer {
|
||||
LoginViewModel()
|
||||
}
|
||||
initializer {
|
||||
AdminUserViewModel(this.createSavedStateHandle(), challengeApplication().container.userRestRepository)
|
||||
}
|
||||
initializer {
|
||||
AdminChallengeViewModel(this.createSavedStateHandle(), challengeApplication().container.challengeRestRepository)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
interface ChallengeRepository {
|
||||
fun getAll(): Flow<PagingData<Challenge>>
|
||||
// fun getByUid(uid: Int): Flow<Challenge?>
|
||||
// suspend fun insert(challenge: Challenge)
|
||||
suspend fun insert(challenge: Challenge)
|
||||
// suspend fun update(challenge: Challenge)
|
||||
// suspend fun delete(challenge: Challenge)
|
||||
}
|
@ -21,7 +21,7 @@ class OfflineChallengeRepository(private val challengeDao: ChallengeDao) : Chall
|
||||
).flow;
|
||||
fun getByUid(uid: Int): Flow<Challenge?> = challengeDao.getByUid(uid);
|
||||
|
||||
suspend fun insert(challenge: Challenge) = challengeDao.insert(challenge);
|
||||
override suspend fun insert(challenge: Challenge) = challengeDao.insert(challenge);
|
||||
|
||||
suspend fun update(challenge: Challenge) = challengeDao.update(challenge);
|
||||
|
||||
|
@ -0,0 +1,93 @@
|
||||
package com.example.myapplication.ui.challenge.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.myapplication.R
|
||||
import com.example.myapplication.common.AppViewModelProvider
|
||||
import com.example.myapplication.database.challenge.model.Challenge
|
||||
import com.example.myapplication.ui.theme.MyApplicationTheme
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AdminChallengeView(
|
||||
navController: NavController,
|
||||
viewModel: AdminChallengeViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
ChallengeEdit(
|
||||
challengeUiState = viewModel.adminChallengeUiState,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
viewModel.saveChallenge()
|
||||
navController.popBackStack()
|
||||
}
|
||||
},
|
||||
onUpdate = viewModel::updateUiState,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ChallengeEdit(
|
||||
challengeUiState: AdminChallengeUiState,
|
||||
onClick: () -> Unit,
|
||||
onUpdate: (AdminChallengeDetails) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 10.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = challengeUiState.adminChallengeDetails.challenge_name,
|
||||
onValueChange = { onUpdate(challengeUiState.adminChallengeDetails.copy(challenge_name = it)) },
|
||||
label = {Text("Задание")},
|
||||
singleLine = true
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = challengeUiState.adminChallengeDetails.category_id.toString(),
|
||||
onValueChange = { onUpdate(challengeUiState.adminChallengeDetails.copy(category_id = it.toInt())) },
|
||||
label = {Text("Категория")},
|
||||
singleLine = true
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = challengeUiState.adminChallengeDetails.user_id.toString(),
|
||||
onValueChange = { onUpdate(challengeUiState.adminChallengeDetails.copy(user_id = it.toInt())) },
|
||||
label = {Text("Пользователь")},
|
||||
singleLine = true
|
||||
)
|
||||
Button(
|
||||
onClick = onClick,
|
||||
shape = MaterialTheme.shapes.small,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "Сохранить")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.example.myapplication.ui.challenge.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.myapplication.common.ChallengeRepository
|
||||
import com.example.myapplication.database.challenge.model.Challenge
|
||||
import com.example.myapplication.database.user.model.UserWithChallenges
|
||||
import com.example.myapplication.common.UserRepository
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AdminChallengeViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val challengeRepository: ChallengeRepository
|
||||
) : ViewModel() {
|
||||
|
||||
var adminChallengeUiState by mutableStateOf(AdminChallengeUiState())
|
||||
private set
|
||||
|
||||
init {
|
||||
|
||||
}
|
||||
|
||||
fun updateUiState(adminChallengeDetails: AdminChallengeDetails) {
|
||||
adminChallengeUiState = AdminChallengeUiState(
|
||||
adminChallengeDetails = adminChallengeDetails,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun saveChallenge() {
|
||||
if (validateInput()) {
|
||||
challengeRepository.insert(
|
||||
adminChallengeUiState.adminChallengeDetails.toChallenge()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateInput(uiState: AdminChallengeDetails = adminChallengeUiState.adminChallengeDetails): Boolean {
|
||||
return with(uiState) {
|
||||
challenge_name.isNotBlank()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class AdminChallengeUiState(
|
||||
val adminChallengeDetails: AdminChallengeDetails = AdminChallengeDetails(),
|
||||
)
|
||||
|
||||
data class AdminChallengeDetails(
|
||||
val challenge_name: String = "",
|
||||
val challenge_status: Boolean = false,
|
||||
val category_id: Int = 0,
|
||||
val user_id: Int = 0,
|
||||
)
|
||||
|
||||
fun AdminChallengeDetails.toChallenge(uid: Int = 0): Challenge = Challenge(
|
||||
challenge_id = uid,
|
||||
challenge_name = challenge_name,
|
||||
challenge_status = challenge_status,
|
||||
category_id = category_id,
|
||||
user_id = user_id,
|
||||
)
|
||||
|
116
app/src/main/java/com/example/myapplication/ui/login/Login.kt
Normal file
116
app/src/main/java/com/example/myapplication/ui/login/Login.kt
Normal file
@ -0,0 +1,116 @@
|
||||
package com.example.myapplication.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.myapplication.R
|
||||
import com.example.myapplication.common.AppViewModelProvider
|
||||
import com.example.myapplication.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.myapplication.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.myapplication.api.MyServerService
|
||||
import com.example.myapplication.api.models.Credentials
|
||||
import com.example.myapplication.ui.navigation.Screen
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -36,6 +36,10 @@ import com.example.myapplication.ui.about.About
|
||||
import com.example.myapplication.ui.theme.MyApplicationTheme
|
||||
import com.example.myapplication.ui.user.view.UserView
|
||||
import com.example.myapplication.ui.user.list.UsersList
|
||||
import com.example.myapplication.ui.login.Login
|
||||
import com.example.myapplication.api.MyServerService
|
||||
import com.example.myapplication.ui.challenge.view.AdminChallengeView
|
||||
import com.example.myapplication.ui.user.view.AdminUserView
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -77,24 +81,27 @@ fun Navbar(
|
||||
) {
|
||||
NavigationBar(modifier) {
|
||||
Screen.bottomBarItems.forEach { screen ->
|
||||
NavigationBarItem(
|
||||
icon = { Icon(screen.icon, contentDescription = null) },
|
||||
label = { Text(stringResource(screen.resourceId)) },
|
||||
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
|
||||
onClick = {
|
||||
navController.navigate(screen.route) {
|
||||
popUpTo(navController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
if (MyServerService.getToken().isBlank() || screen.route !== "login") {
|
||||
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
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun Navhost(
|
||||
navController: NavHostController,
|
||||
@ -109,11 +116,18 @@ fun Navhost(
|
||||
composable(Screen.CategoryList.route) { CategoriesList(navController) }
|
||||
composable(Screen.About.route) { About() }
|
||||
composable(Screen.UserList.route) { UsersList(navController) }
|
||||
composable(Screen.Login.route) { Login(navController) }
|
||||
composable(Screen.AdminChallengeView.route){ AdminChallengeView(navController)}
|
||||
composable(
|
||||
Screen.UserView.route,
|
||||
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||
) { backStackEntry ->
|
||||
backStackEntry.arguments?.let { UserView() }
|
||||
if (MyServerService.getToken().isBlank()) {
|
||||
backStackEntry.arguments?.let { UserView() }
|
||||
}
|
||||
else {
|
||||
backStackEntry.arguments?.let { AdminUserView(navController) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import androidx.compose.material.icons.filled.List
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.example.myapplication.R
|
||||
import androidx.compose.material.icons.filled.Face
|
||||
import com.example.myapplication.api.MyServerService
|
||||
|
||||
enum class Screen(
|
||||
val route: String,
|
||||
@ -18,6 +20,9 @@ enum class Screen(
|
||||
CategoryList(
|
||||
"categories-list", R.string.categories_list, Icons.Filled.List
|
||||
),
|
||||
Login(
|
||||
"login", R.string.login_main_title, Icons.Filled.Face
|
||||
),
|
||||
UserList(
|
||||
"users-list", R.string.user_list, Icons.Filled.Person
|
||||
),
|
||||
@ -26,13 +31,17 @@ enum class Screen(
|
||||
),
|
||||
UserView(
|
||||
"user-view/{id}", R.string.user_view_title, showInBottomBar = false
|
||||
),
|
||||
AdminChallengeView(
|
||||
"admin-challenge-view", R.string.admin_challenge,showInBottomBar = false
|
||||
);
|
||||
|
||||
companion object {
|
||||
val bottomBarItems = listOf(
|
||||
About,
|
||||
CategoryList,
|
||||
UserList,
|
||||
About,
|
||||
Login
|
||||
)
|
||||
|
||||
fun getItem(route: String): Screen? {
|
||||
|
@ -0,0 +1,124 @@
|
||||
package com.example.myapplication.ui.user.view
|
||||
|
||||
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.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
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.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.FloatingActionButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
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.myapplication.R
|
||||
import com.example.myapplication.api.MyServerService
|
||||
import com.example.myapplication.database.user.model.User
|
||||
import com.example.myapplication.common.AppViewModelProvider
|
||||
import com.example.myapplication.ui.navigation.Screen
|
||||
import com.example.myapplication.ui.theme.MyApplicationTheme
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AdminUserView(
|
||||
navController: NavController,
|
||||
viewModel: AdminUserViewModel = viewModel(factory = AppViewModelProvider.Factory)
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val userWithChallanges = viewModel.adminUserUiState.adminUserDetails
|
||||
Scaffold(
|
||||
topBar = {},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
val route = Screen.AdminChallengeView.route.replace("{id}", 0.toString())
|
||||
navController.navigate(route)
|
||||
},
|
||||
) {
|
||||
Icon(Icons.Filled.Add, "Добавить")
|
||||
}
|
||||
}
|
||||
)
|
||||
{ innerPadding ->
|
||||
val userWithChallanges = viewModel.adminUserUiState.adminUserDetails;
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = userWithChallanges.login.toString(),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = {
|
||||
Text(stringResource(id = R.string.user_login))
|
||||
}
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = userWithChallanges.fio.toString(),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = {
|
||||
Text(stringResource(id = R.string.user_fio))
|
||||
}
|
||||
)
|
||||
userWithChallanges.challenges?.forEachIndexed() { _, challenge ->
|
||||
Row {
|
||||
Text(text = challenge.challenge_name)
|
||||
if (challenge.challenge_status) {
|
||||
Text(text = " - Выполнено")
|
||||
} else {
|
||||
Text(text = " - В процессе")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.example.myapplication.ui.user.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.myapplication.database.challenge.model.Challenge
|
||||
import com.example.myapplication.database.user.model.UserWithChallenges
|
||||
import com.example.myapplication.common.UserRepository
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AdminUserViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val userRepository: UserRepository
|
||||
) : ViewModel() {
|
||||
|
||||
var adminUserUiState by mutableStateOf(AdminUserUiState())
|
||||
private set
|
||||
|
||||
private val studentUid: Int = checkNotNull(savedStateHandle["id"])
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
if (studentUid > 0) {
|
||||
adminUserUiState = userRepository.getByUid(studentUid)
|
||||
.toAdminUiState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class AdminUserUiState(
|
||||
val adminUserDetails: AdminUserDetails = AdminUserDetails(),
|
||||
)
|
||||
|
||||
data class AdminUserDetails(
|
||||
val login: String = "",
|
||||
val fio: String = "",
|
||||
val challenges: List<Challenge> = listOf()
|
||||
)
|
||||
|
||||
fun UserWithChallenges.toAdminDetails(): AdminUserDetails = AdminUserDetails(
|
||||
login = user.login,
|
||||
fio = user.fio,
|
||||
challenges = challenges
|
||||
)
|
||||
|
||||
fun UserWithChallenges.toAdminUiState(): AdminUserUiState = AdminUserUiState(
|
||||
adminUserDetails = this.toAdminDetails(),
|
||||
)
|
@ -10,4 +10,8 @@
|
||||
<string name="user_login">Логин</string>
|
||||
<string name="user_fio">ФИО</string>
|
||||
<string name="user_view_title">Пользователь</string>
|
||||
<string name="login_main_title">Вход</string>
|
||||
<string name="login">Логин</string>
|
||||
<string name="password">Пароль</string>
|
||||
<string name="admin_challenge">Создание челленджа</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user