diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 394f00f..5315ee6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") diff --git a/app/src/main/java/com/example/myapplication/api/MyServerService.kt b/app/src/main/java/com/example/myapplication/api/MyServerService.kt index 2115f4b..ba87428 100644 --- a/app/src/main/java/com/example/myapplication/api/MyServerService.kt +++ b/app/src/main/java/com/example/myapplication/api/MyServerService.kt @@ -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 @GET("challenges") suspend fun getChallenges( - @Query("_page") page: Int, + @Query("page") page: Int, @Query("_limit") limit: Int, ): List @GET("categories") suspend fun getCategories( - @Query("_page") page: Int, + @Query("page") page: Int, @Query("_limit") limit: Int, ): List @@ -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) diff --git a/app/src/main/java/com/example/myapplication/api/categories/CategoryRemoteMediator.kt b/app/src/main/java/com/example/myapplication/api/categories/CategoryRemoteMediator.kt index 64a31f3..fc3c673 100644 --- a/app/src/main/java/com/example/myapplication/api/categories/CategoryRemoteMediator.kt +++ b/app/src/main/java/com/example/myapplication/api/categories/CategoryRemoteMediator.kt @@ -70,8 +70,7 @@ import java.io.IOException nextKey = nextKey ) } - dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CATEGORY) - dbCategoryRepository.clearCategories() + dbRemoteKeyRepository.createRemoteKeys(keys) dbCategoryRepository.insertCategories(categories) } diff --git a/app/src/main/java/com/example/myapplication/api/challenges/ChallengeRemoteMediator.kt b/app/src/main/java/com/example/myapplication/api/challenges/ChallengeRemoteMediator.kt index 7f2b3d6..418f289 100644 --- a/app/src/main/java/com/example/myapplication/api/challenges/ChallengeRemoteMediator.kt +++ b/app/src/main/java/com/example/myapplication/api/challenges/ChallengeRemoteMediator.kt @@ -77,8 +77,6 @@ class ChallengeRemoteMediator( ) } - dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.CHALLENGE) - dbChallengeRepository.clearChallenges() dbUserRepository.getAll() dbCategoryRepository.getAll() dbRemoteKeyRepository.createRemoteKeys(keys) diff --git a/app/src/main/java/com/example/myapplication/api/challenges/RestChallengeRepository.kt b/app/src/main/java/com/example/myapplication/api/challenges/RestChallengeRepository.kt index 159df94..8d63feb 100644 --- a/app/src/main/java/com/example/myapplication/api/challenges/RestChallengeRepository.kt +++ b/app/src/main/java/com/example/myapplication/api/challenges/RestChallengeRepository.kt @@ -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()) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/models/ChallengeRemote.kt b/app/src/main/java/com/example/myapplication/api/models/ChallengeRemote.kt index f869816..95aadda 100644 --- a/app/src/main/java/com/example/myapplication/api/models/ChallengeRemote.kt +++ b/app/src/main/java/com/example/myapplication/api/models/ChallengeRemote.kt @@ -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!!, ) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/models/Credentials.kt b/app/src/main/java/com/example/myapplication/api/models/Credentials.kt new file mode 100644 index 0000000..c0a951a --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/models/Credentials.kt @@ -0,0 +1,9 @@ +package com.example.myapplication.api.models + +import kotlinx.serialization.Serializable + +@Serializable +data class Credentials( + val email: String = "", + val password: String = "", +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/models/Token.kt b/app/src/main/java/com/example/myapplication/api/models/Token.kt new file mode 100644 index 0000000..3c0a3f0 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/models/Token.kt @@ -0,0 +1,8 @@ +package com.example.myapplication.api.models + +import kotlinx.serialization.Serializable + +@Serializable +data class Token( + val token: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/users/UserRemoteMediator.kt b/app/src/main/java/com/example/myapplication/api/users/UserRemoteMediator.kt index 198deb5..2e159b8 100644 --- a/app/src/main/java/com/example/myapplication/api/users/UserRemoteMediator.kt +++ b/app/src/main/java/com/example/myapplication/api/users/UserRemoteMediator.kt @@ -74,8 +74,7 @@ class UserRemoteMediator ( nextKey = nextKey ) } - dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.USER) - dbUserRepository.clearUsers() + dbRemoteKeyRepository.createRemoteKeys(keys) dbChallengeRepository.getAll() dbUserRepository.insertUsers(users) diff --git a/app/src/main/java/com/example/myapplication/common/AppViewModelProvider.kt b/app/src/main/java/com/example/myapplication/common/AppViewModelProvider.kt index 557c2ab..a4a6e47 100644 --- a/app/src/main/java/com/example/myapplication/common/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/myapplication/common/AppViewModelProvider.kt @@ -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) + } } } diff --git a/app/src/main/java/com/example/myapplication/common/ChallengeRepository.kt b/app/src/main/java/com/example/myapplication/common/ChallengeRepository.kt index abd1a4c..6194cf7 100644 --- a/app/src/main/java/com/example/myapplication/common/ChallengeRepository.kt +++ b/app/src/main/java/com/example/myapplication/common/ChallengeRepository.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow interface ChallengeRepository { fun getAll(): Flow> // fun getByUid(uid: Int): Flow -// suspend fun insert(challenge: Challenge) + suspend fun insert(challenge: Challenge) // suspend fun update(challenge: Challenge) // suspend fun delete(challenge: Challenge) } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/challenge/repository/OfflineChallengeRepository.kt b/app/src/main/java/com/example/myapplication/database/challenge/repository/OfflineChallengeRepository.kt index 18a8542..294d1b8 100644 --- a/app/src/main/java/com/example/myapplication/database/challenge/repository/OfflineChallengeRepository.kt +++ b/app/src/main/java/com/example/myapplication/database/challenge/repository/OfflineChallengeRepository.kt @@ -21,7 +21,7 @@ class OfflineChallengeRepository(private val challengeDao: ChallengeDao) : Chall ).flow; fun getByUid(uid: Int): Flow = 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); diff --git a/app/src/main/java/com/example/myapplication/ui/challenge/view/AdminChallengeView.kt b/app/src/main/java/com/example/myapplication/ui/challenge/view/AdminChallengeView.kt new file mode 100644 index 0000000..52bbcee --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/challenge/view/AdminChallengeView.kt @@ -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 = "Сохранить") + } + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/challenge/view/AdminChallengeViewModel.kt b/app/src/main/java/com/example/myapplication/ui/challenge/view/AdminChallengeViewModel.kt new file mode 100644 index 0000000..d2f4da9 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/challenge/view/AdminChallengeViewModel.kt @@ -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, +) + diff --git a/app/src/main/java/com/example/myapplication/ui/login/Login.kt b/app/src/main/java/com/example/myapplication/ui/login/Login.kt new file mode 100644 index 0000000..a2cf815 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/login/Login.kt @@ -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), + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/ui/login/LoginViewModel.kt b/app/src/main/java/com/example/myapplication/ui/login/LoginViewModel.kt new file mode 100644 index 0000000..c66d06d --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/login/LoginViewModel.kt @@ -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() + val successState: LiveData + 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 + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/ui/navigation/MainNavbar.kt b/app/src/main/java/com/example/myapplication/ui/navigation/MainNavbar.kt index 7810663..0e361dd 100644 --- a/app/src/main/java/com/example/myapplication/ui/navigation/MainNavbar.kt +++ b/app/src/main/java/com/example/myapplication/ui/navigation/MainNavbar.kt @@ -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) } + } } } } diff --git a/app/src/main/java/com/example/myapplication/ui/navigation/Screen.kt b/app/src/main/java/com/example/myapplication/ui/navigation/Screen.kt index 7b45fa0..5d2bb92 100644 --- a/app/src/main/java/com/example/myapplication/ui/navigation/Screen.kt +++ b/app/src/main/java/com/example/myapplication/ui/navigation/Screen.kt @@ -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? { diff --git a/app/src/main/java/com/example/myapplication/ui/user/view/AdminUserView.kt b/app/src/main/java/com/example/myapplication/ui/user/view/AdminUserView.kt new file mode 100644 index 0000000..9e2c2d4 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/user/view/AdminUserView.kt @@ -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 = " - В процессе") + } + } + } + } + } +} diff --git a/app/src/main/java/com/example/myapplication/ui/user/view/AdminUserViewModel.kt b/app/src/main/java/com/example/myapplication/ui/user/view/AdminUserViewModel.kt new file mode 100644 index 0000000..1ab79c9 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/ui/user/view/AdminUserViewModel.kt @@ -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 = listOf() + ) + + fun UserWithChallenges.toAdminDetails(): AdminUserDetails = AdminUserDetails( + login = user.login, + fio = user.fio, + challenges = challenges + ) + + fun UserWithChallenges.toAdminUiState(): AdminUserUiState = AdminUserUiState( + adminUserDetails = this.toAdminDetails(), + ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1d4ac4..0ef4fb9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,4 +10,8 @@ Логин ФИО Пользователь + Вход + Логин + Пароль + Создание челленджа \ No newline at end of file