final variant
This commit is contained in:
parent
0d0ffbeb2f
commit
f0ce6018a9
@ -53,6 +53,8 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("androidx.compose.runtime:runtime-livedata")
|
||||||
|
|
||||||
//Core
|
//Core
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
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.UserRemote
|
||||||
import com.example.myapplication.api.models.ChallengeRemote
|
import com.example.myapplication.api.models.ChallengeRemote
|
||||||
import com.example.myapplication.api.models.CategoryRemote
|
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 {
|
interface MyServerService {
|
||||||
@GET("users")
|
@GET("users")
|
||||||
suspend fun getUsers(
|
suspend fun getUsers(
|
||||||
@Query("_page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("_limit") limit: Int,
|
@Query("_limit") limit: Int,
|
||||||
): List<UserRemote>
|
): List<UserRemote>
|
||||||
|
|
||||||
@GET("challenges")
|
@GET("challenges")
|
||||||
suspend fun getChallenges(
|
suspend fun getChallenges(
|
||||||
@Query("_page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("_limit") limit: Int,
|
@Query("_limit") limit: Int,
|
||||||
): List<ChallengeRemote>
|
): List<ChallengeRemote>
|
||||||
|
|
||||||
@GET("categories")
|
@GET("categories")
|
||||||
suspend fun getCategories(
|
suspend fun getCategories(
|
||||||
@Query("_page") page: Int,
|
@Query("page") page: Int,
|
||||||
@Query("_limit") limit: Int,
|
@Query("_limit") limit: Int,
|
||||||
): List<CategoryRemote>
|
): List<CategoryRemote>
|
||||||
|
|
||||||
@ -41,8 +48,27 @@ interface MyServerService {
|
|||||||
@Path("id") id: Int,
|
@Path("id") id: Int,
|
||||||
): ChallengeRemote
|
): ChallengeRemote
|
||||||
|
|
||||||
|
@POST("login")
|
||||||
|
suspend fun login(
|
||||||
|
@Body credentials: Credentials
|
||||||
|
): Token
|
||||||
|
|
||||||
|
@POST("challenges")
|
||||||
|
suspend fun createChallenge(
|
||||||
|
@Body challenge: ChallengeRemote,
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BASE_URL = "http://10.0.2.2:8000/api/"
|
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
|
@Volatile
|
||||||
private var INSTANCE: MyServerService? = null
|
private var INSTANCE: MyServerService? = null
|
||||||
@ -52,7 +78,26 @@ interface MyServerService {
|
|||||||
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)
|
||||||
|
@ -70,8 +70,7 @@ import java.io.IOException
|
|||||||
nextKey = nextKey
|
nextKey = nextKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CATEGORY)
|
|
||||||
dbCategoryRepository.clearCategories()
|
|
||||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||||
dbCategoryRepository.insertCategories(categories)
|
dbCategoryRepository.insertCategories(categories)
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,6 @@ class ChallengeRemoteMediator(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CHALLENGE)
|
|
||||||
dbChallengeRepository.clearChallenges()
|
|
||||||
dbUserRepository.getAll()
|
dbUserRepository.getAll()
|
||||||
dbCategoryRepository.getAll()
|
dbCategoryRepository.getAll()
|
||||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||||
|
@ -5,6 +5,7 @@ import androidx.paging.Pager
|
|||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import com.example.myapplication.api.MyServerService
|
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.models.toUserWithChallenges
|
||||||
import com.example.myapplication.api.users.UserRemoteMediator
|
import com.example.myapplication.api.users.UserRemoteMediator
|
||||||
import com.example.myapplication.common.AppDataContainer
|
import com.example.myapplication.common.AppDataContainer
|
||||||
@ -49,4 +50,8 @@ class RestChallengeRepository(
|
|||||||
pagingSourceFactory = pagingSourceFactory
|
pagingSourceFactory = pagingSourceFactory
|
||||||
).flow
|
).flow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun insert(challenge: Challenge) {
|
||||||
|
service.createChallenge(challenge.toChallengeRemote())
|
||||||
|
}
|
||||||
}
|
}
|
@ -19,4 +19,12 @@ fun ChallengeRemote.toChallenge(): Challenge = Challenge(
|
|||||||
challenge_status,
|
challenge_status,
|
||||||
category_id,
|
category_id,
|
||||||
user_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
|
nextKey = nextKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.USER)
|
|
||||||
dbUserRepository.clearUsers()
|
|
||||||
dbRemoteKeyRepository.createRemoteKeys(keys)
|
dbRemoteKeyRepository.createRemoteKeys(keys)
|
||||||
dbChallengeRepository.getAll()
|
dbChallengeRepository.getAll()
|
||||||
dbUserRepository.insertUsers(users)
|
dbUserRepository.insertUsers(users)
|
||||||
|
@ -7,8 +7,11 @@ import androidx.lifecycle.viewmodel.initializer
|
|||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import com.example.myapplication.ChallengeApplication
|
import com.example.myapplication.ChallengeApplication
|
||||||
import com.example.myapplication.ui.category.list.CategoryListViewModel
|
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.list.UserListViewModel
|
||||||
import com.example.myapplication.ui.user.view.UserViewModel
|
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 {
|
object AppViewModelProvider {
|
||||||
val Factory = viewModelFactory {
|
val Factory = viewModelFactory {
|
||||||
@ -21,6 +24,15 @@ object AppViewModelProvider {
|
|||||||
initializer {
|
initializer {
|
||||||
UserViewModel(this.createSavedStateHandle(), challengeApplication().container.userRestRepository)
|
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 {
|
interface ChallengeRepository {
|
||||||
fun getAll(): Flow<PagingData<Challenge>>
|
fun getAll(): Flow<PagingData<Challenge>>
|
||||||
// fun getByUid(uid: Int): Flow<Challenge?>
|
// fun getByUid(uid: Int): Flow<Challenge?>
|
||||||
// suspend fun insert(challenge: Challenge)
|
suspend fun insert(challenge: Challenge)
|
||||||
// suspend fun update(challenge: Challenge)
|
// suspend fun update(challenge: Challenge)
|
||||||
// suspend fun delete(challenge: Challenge)
|
// suspend fun delete(challenge: Challenge)
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ class OfflineChallengeRepository(private val challengeDao: ChallengeDao) : Chall
|
|||||||
).flow;
|
).flow;
|
||||||
fun getByUid(uid: Int): Flow<Challenge?> = challengeDao.getByUid(uid);
|
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);
|
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.theme.MyApplicationTheme
|
||||||
import com.example.myapplication.ui.user.view.UserView
|
import com.example.myapplication.ui.user.view.UserView
|
||||||
import com.example.myapplication.ui.user.list.UsersList
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@ -77,24 +81,27 @@ fun Navbar(
|
|||||||
) {
|
) {
|
||||||
NavigationBar(modifier) {
|
NavigationBar(modifier) {
|
||||||
Screen.bottomBarItems.forEach { screen ->
|
Screen.bottomBarItems.forEach { screen ->
|
||||||
NavigationBarItem(
|
if (MyServerService.getToken().isBlank() || screen.route !== "login") {
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Navhost(
|
fun Navhost(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
@ -109,11 +116,18 @@ fun Navhost(
|
|||||||
composable(Screen.CategoryList.route) { CategoriesList(navController) }
|
composable(Screen.CategoryList.route) { CategoriesList(navController) }
|
||||||
composable(Screen.About.route) { About() }
|
composable(Screen.About.route) { About() }
|
||||||
composable(Screen.UserList.route) { UsersList(navController) }
|
composable(Screen.UserList.route) { UsersList(navController) }
|
||||||
|
composable(Screen.Login.route) { Login(navController) }
|
||||||
|
composable(Screen.AdminChallengeView.route){ AdminChallengeView(navController)}
|
||||||
composable(
|
composable(
|
||||||
Screen.UserView.route,
|
Screen.UserView.route,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
arguments = listOf(navArgument("id") { type = NavType.IntType })
|
||||||
) { backStackEntry ->
|
) { 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.material.icons.filled.Person
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import com.example.myapplication.R
|
import com.example.myapplication.R
|
||||||
|
import androidx.compose.material.icons.filled.Face
|
||||||
|
import com.example.myapplication.api.MyServerService
|
||||||
|
|
||||||
enum class Screen(
|
enum class Screen(
|
||||||
val route: String,
|
val route: String,
|
||||||
@ -18,6 +20,9 @@ enum class Screen(
|
|||||||
CategoryList(
|
CategoryList(
|
||||||
"categories-list", R.string.categories_list, Icons.Filled.List
|
"categories-list", R.string.categories_list, Icons.Filled.List
|
||||||
),
|
),
|
||||||
|
Login(
|
||||||
|
"login", R.string.login_main_title, Icons.Filled.Face
|
||||||
|
),
|
||||||
UserList(
|
UserList(
|
||||||
"users-list", R.string.user_list, Icons.Filled.Person
|
"users-list", R.string.user_list, Icons.Filled.Person
|
||||||
),
|
),
|
||||||
@ -26,13 +31,17 @@ enum class Screen(
|
|||||||
),
|
),
|
||||||
UserView(
|
UserView(
|
||||||
"user-view/{id}", R.string.user_view_title, showInBottomBar = false
|
"user-view/{id}", R.string.user_view_title, showInBottomBar = false
|
||||||
|
),
|
||||||
|
AdminChallengeView(
|
||||||
|
"admin-challenge-view", R.string.admin_challenge,showInBottomBar = false
|
||||||
);
|
);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val bottomBarItems = listOf(
|
val bottomBarItems = listOf(
|
||||||
|
About,
|
||||||
CategoryList,
|
CategoryList,
|
||||||
UserList,
|
UserList,
|
||||||
About,
|
Login
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getItem(route: String): Screen? {
|
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_login">Логин</string>
|
||||||
<string name="user_fio">ФИО</string>
|
<string name="user_fio">ФИО</string>
|
||||||
<string name="user_view_title">Пользователь</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>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user