diff --git a/Server/data.json b/Server/data.json index 44a24d0..2d55f88 100644 --- a/Server/data.json +++ b/Server/data.json @@ -1,11 +1,11 @@ { "users": [ { + "id": 1, "login": "Admin", "password": "Admin", "email": "admin@mail.ru", - "role": "ADMIN", - "id": 1 + "role": "ADMIN" }, { "id": 2, @@ -13,6 +13,13 @@ "password": "123", "email": "user1@mail.ru", "role": "USER" + }, + { + "id": 3, + "login": "User2", + "password": "123", + "email": "user2@gmail.ru", + "role": "USER" } ], "authors": [ @@ -3957,7 +3964,7 @@ -39 ], "authorId": 2, - "userId": 2 + "userId": 1 }, { "title": "Вельд", @@ -7316,7 +7323,7 @@ -39 ], "authorId": 2, - "userId": 2, + "userId": 1, "id": 4 }, { diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 07b9bb9..e40b67b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") - // LiveData | + + // LiveData | DataStore implementation("androidx.compose.runtime:runtime-livedata:1.5.4") + implementation("androidx.datastore:datastore-preferences:1.0.0") } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/MainActivity.kt b/app/src/main/java/com/example/myapplication/MainActivity.kt index dd2c102..f250836 100644 --- a/app/src/main/java/com/example/myapplication/MainActivity.kt +++ b/app/src/main/java/com/example/myapplication/MainActivity.kt @@ -11,6 +11,7 @@ import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import com.example.myapplication.composeui.authenticator.Authenticator import com.example.myapplication.composeui.navigation.MainNavbar import com.example.myapplication.ui.theme.MyApplicationTheme @@ -24,7 +25,8 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - MainNavbar() + Authenticator() + MainNavbar(); } } } diff --git a/app/src/main/java/com/example/myapplication/SingletonClass.kt b/app/src/main/java/com/example/myapplication/SingletonClass.kt deleted file mode 100644 index d7593d3..0000000 --- a/app/src/main/java/com/example/myapplication/SingletonClass.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.myapplication - -class SingletonClass{ - var _userId = 2 - var _role = "USER" - fun getUserId(): Int{ return _userId } - fun setUserId(userId: Int){ - _userId = userId - } - fun getRole(): String{ return _role } - fun setRole(role: String){ - _role = role - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/api/ServerService.kt b/app/src/main/java/com/example/myapplication/api/ServerService.kt index 553346c..2a6b604 100644 --- a/app/src/main/java/com/example/myapplication/api/ServerService.kt +++ b/app/src/main/java/com/example/myapplication/api/ServerService.kt @@ -100,7 +100,7 @@ interface ServerService{ suspend fun getUserByLoginPass( @Query("login") login: String, @Query("password") password: String - ): UserRemote + ): List @POST("users") suspend fun createUser( @Body user: UserRemote, @@ -117,7 +117,7 @@ interface ServerService{ @Path("id") id: Int, ): UserRemote companion object { - private const val BASE_URL = "http://89.239.172.45:8079/" + private const val BASE_URL = "http://89.239.174.67:8079/" @Volatile private var INSTANCE: ServerService? = null diff --git a/app/src/main/java/com/example/myapplication/api/respositories/RestUserRepository.kt b/app/src/main/java/com/example/myapplication/api/respositories/RestUserRepository.kt index 5988667..4259f35 100644 --- a/app/src/main/java/com/example/myapplication/api/respositories/RestUserRepository.kt +++ b/app/src/main/java/com/example/myapplication/api/respositories/RestUserRepository.kt @@ -28,7 +28,7 @@ class RestUserRepository( } override suspend fun tryLogin(login: String, password: String): User? { - return service.getUserByLoginPass(login, password).toUser() + return service.getUserByLoginPass(login, password).firstOrNull()?.toUser() } override suspend fun insert(user: User) { diff --git a/app/src/main/java/com/example/myapplication/composeui/AppViewModelProvider.kt b/app/src/main/java/com/example/myapplication/composeui/AppViewModelProvider.kt index b5e61ed..bcfb67a 100644 --- a/app/src/main/java/com/example/myapplication/composeui/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/myapplication/composeui/AppViewModelProvider.kt @@ -6,15 +6,19 @@ import androidx.lifecycle.viewmodel.CreationExtras import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.example.myapplication.MyApplication +import com.example.myapplication.composeui.ViewModel.AuthorBooksViewModel import com.example.myapplication.composeui.ViewModel.AuthorDropDownViewModel import com.example.myapplication.composeui.ViewModel.AuthorEditViewModel import com.example.myapplication.composeui.ViewModel.AuthorListViewModel import com.example.myapplication.composeui.ViewModel.BookEditViewModel import com.example.myapplication.composeui.ViewModel.BookListViewModel import com.example.myapplication.composeui.ViewModel.BookPageViewModel +import com.example.myapplication.composeui.ViewModel.LoginViewModel import com.example.myapplication.composeui.ViewModel.SearchPageViewModel +import com.example.myapplication.composeui.ViewModel.UserBooksViewModel import com.example.myapplication.composeui.ViewModel.UserEditViewModel import com.example.myapplication.composeui.ViewModel.UserPageViewModel +import com.example.myapplication.composeui.authenticator.AuthenticatorViewModel object AppViewModelProvider { val Factory = viewModelFactory { @@ -69,6 +73,21 @@ object AppViewModelProvider { myApplication().container.authorRestRepository ) } + initializer { + AuthorBooksViewModel( + this.createSavedStateHandle(), + myApplication().container.bookRestRepository + ) + } + initializer { + UserBooksViewModel(myApplication().container.bookRestRepository) + } + initializer { + AuthenticatorViewModel(myApplication().container.userRestRepository) + } + initializer { + LoginViewModel(myApplication().container.userRestRepository) + } } } diff --git a/app/src/main/java/com/example/myapplication/composeui/AuthorBooks.kt b/app/src/main/java/com/example/myapplication/composeui/AuthorBooks.kt new file mode 100644 index 0000000..45e3fd5 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/composeui/AuthorBooks.kt @@ -0,0 +1,52 @@ +package com.example.myapplication.composeui + +import android.content.res.Configuration +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import com.example.myapplication.composeui.ViewModel.AuthorBooksViewModel +import com.example.myapplication.db.model.Book +import com.example.myapplication.ui.theme.MyApplicationTheme +import kotlinx.coroutines.launch + +@Composable +fun AuthorBooks(navController: NavController, authorId: Int, viewModel: AuthorBooksViewModel = viewModel(factory = AppViewModelProvider.Factory)) { + val coroutineScope = rememberCoroutineScope() + val authorBooksUiState = viewModel.authorBooksUiState + val pagingBook: LazyPagingItems = viewModel.bookPagedData.collectAsLazyPagingItems() + LaunchedEffect(Unit) { + viewModel.refreshState() + } + fun refresh(){ + coroutineScope.launch { viewModel.refreshState() } + } + LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 180.dp)){ + items (pagingBook.itemCount){ + index -> BookCell(navController = navController, book = pagingBook[index]!!.copy()) + } + } +} + +@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun AuthorBooksPreview() { + MyApplicationTheme { + Surface( + color = MaterialTheme.colorScheme.background + ) { + AuthorBooks(navController = rememberNavController(), 0) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/AuthorCell.kt b/app/src/main/java/com/example/myapplication/composeui/AuthorCell.kt index 95ffe12..cc31684 100644 --- a/app/src/main/java/com/example/myapplication/composeui/AuthorCell.kt +++ b/app/src/main/java/com/example/myapplication/composeui/AuthorCell.kt @@ -27,14 +27,17 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.example.myapplication.MainActivity +import com.example.myapplication.R import com.example.myapplication.composeui.ViewModel.AuthorListViewModel import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.db.database.AppDatabase import com.example.myapplication.db.model.Author +import com.example.myapplication.store.LiveStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch @@ -46,7 +49,7 @@ fun AuthorCell(navController: NavController?, author: Author, viewModel: AuthorL val context = LocalContext.current Column(modifier = Modifier .padding(all = 5.dp) - .requiredSize(170.dp, 100.dp) + .requiredSize(170.dp, 150.dp) .clip(RoundedCornerShape(10.dp)) .border(1.dp, Color.LightGray, shape = RoundedCornerShape(10.dp)) ) { @@ -83,6 +86,14 @@ fun AuthorCell(navController: NavController?, author: Author, viewModel: AuthorL ) } } - + Button( + modifier = Modifier + .fillMaxWidth() + .padding(all = 10.dp), + onClick = { + navController?.navigate(Screen.AuthorBooks.route.replace("{id}", author.id.toString())) + }) { + Text(stringResource(id = R.string.books)) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/AuthorEdit.kt b/app/src/main/java/com/example/myapplication/composeui/AuthorEdit.kt index d06dd79..5247c96 100644 --- a/app/src/main/java/com/example/myapplication/composeui/AuthorEdit.kt +++ b/app/src/main/java/com/example/myapplication/composeui/AuthorEdit.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.example.myapplication.R -import com.example.myapplication.SingletonClass import com.example.myapplication.composeui.ViewModel.AuthorsListUiState import com.example.myapplication.composeui.ViewModel.AuthorsUiState import com.example.myapplication.composeui.ViewModel.AuthorDetails diff --git a/app/src/main/java/com/example/myapplication/composeui/BookEdit.kt b/app/src/main/java/com/example/myapplication/composeui/BookEdit.kt index 6917b38..e1db5c4 100644 --- a/app/src/main/java/com/example/myapplication/composeui/BookEdit.kt +++ b/app/src/main/java/com/example/myapplication/composeui/BookEdit.kt @@ -8,15 +8,12 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextField @@ -33,7 +30,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.example.myapplication.R -import com.example.myapplication.SingletonClass import com.example.myapplication.composeui.ViewModel.AuthorDropDownViewModel import com.example.myapplication.composeui.ViewModel.AuthorsListUiState import com.example.myapplication.composeui.ViewModel.AuthorsUiState @@ -42,6 +38,7 @@ import com.example.myapplication.composeui.ViewModel.BookEditViewModel import com.example.myapplication.composeui.ViewModel.BookUiState import com.example.myapplication.composeui.ViewModel.ImageUploader import com.example.myapplication.db.model.Author +import com.example.myapplication.store.LiveStore import kotlinx.coroutines.launch @Composable @@ -124,8 +121,7 @@ private fun BookEdit( ) ) } - var SingletonClass = SingletonClass() - onUpdate(bookUiState.bookDetails.copy(userId = SingletonClass.getUserId())) + onUpdate(bookUiState.bookDetails.copy(userId = LiveStore.getUserId())) Column( Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/example/myapplication/composeui/Enter.kt b/app/src/main/java/com/example/myapplication/composeui/Enter.kt index d64648e..b8d460b 100644 --- a/app/src/main/java/com/example/myapplication/composeui/Enter.kt +++ b/app/src/main/java/com/example/myapplication/composeui/Enter.kt @@ -6,14 +6,19 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton 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.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -25,41 +30,52 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.rememberNavController import com.example.myapplication.MainActivity -import com.example.myapplication.SingletonClass -import com.example.myapplication.api.ServerService +import com.example.myapplication.composeui.ViewModel.LoginViewModel import com.example.myapplication.composeui.navigation.Screen -import com.example.myapplication.db.database.AppDatabase -import com.example.myapplication.db.model.User +import com.example.myapplication.store.PreferencesStore import com.example.myapplication.ui.theme.MyApplicationTheme -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import kotlinx.coroutines.launch -import kotlinx.coroutines.GlobalScope @OptIn(ExperimentalMaterial3Api::class) @Composable -fun Enter(navController: NavController) { - var SingletonClass = SingletonClass() +fun Enter(navController: NavController, viewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory)) { val scope = rememberCoroutineScope() val context = LocalContext.current - val service: ServerService - var login by remember{mutableStateOf("")} - var password by remember{mutableStateOf("")} + val user = viewModel.signInUserState Column( Modifier .fillMaxWidth() .padding(all = 40.dp)) { Text(stringResource(id = R.string.login)) OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = login, onValueChange = { login = it }, + value = user.details.login, onValueChange = { viewModel.updateState(user.details.copy(login = it)) }, ) Spacer(Modifier.padding(bottom = 10.dp)) Text(stringResource(id = R.string.password)) + var passwordVisible by rememberSaveable { mutableStateOf(false) } OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = password, onValueChange = { password = it }, + value = user.details.password, onValueChange = { viewModel.updateState(user.details.copy(password = it)) }, + visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + trailingIcon = { + val image = if (passwordVisible) + Icons.Filled.Close + else Icons.Filled.Check + + val description = if (passwordVisible) "Hide password" else "Show password" + + IconButton(onClick = { passwordVisible = !passwordVisible }) { + Icon(imageVector = image, description) + } + } ) Spacer(Modifier.padding(bottom = 20.dp)) Button( @@ -68,15 +84,13 @@ fun Enter(navController: NavController) { .padding(all = 10.dp), onClick = { scope.launch { - //if( AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString()) != null){ - // SingletonClass.setUserId(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.uid!!) - //SingletonClass.setRole(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.role!!) - navController?.navigate(Screen.Profile.route) - //} - //else{ - // val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT) - // toast.show() - //} + if(viewModel.signIn(context) != null) { + navController?.navigate(Screen.Profile.route) + } + else{ + val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT) + toast.show() + } } }) { Text(stringResource(id = R.string.enter)) diff --git a/app/src/main/java/com/example/myapplication/composeui/Profile.kt b/app/src/main/java/com/example/myapplication/composeui/Profile.kt index 216fa1e..8939f3c 100644 --- a/app/src/main/java/com/example/myapplication/composeui/Profile.kt +++ b/app/src/main/java/com/example/myapplication/composeui/Profile.kt @@ -1,66 +1,33 @@ package com.example.myapplication.composeui -import android.annotation.SuppressLint import android.content.res.Configuration -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Create -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.outlined.Clear -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.example.myapplication.composeui.navigation.Screen -import com.example.myapplication.db.database.AppDatabase -import com.example.myapplication.db.model.User import com.example.myapplication.R -import com.example.myapplication.SingletonClass -import com.example.myapplication.composeui.ViewModel.UserPageViewModel +import com.example.myapplication.store.LiveStore import com.example.myapplication.ui.theme.MyApplicationTheme -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext @Composable fun Profile(navController: NavController) { - var SingletonClass = SingletonClass() Column(Modifier.padding(all = 40.dp)) { Image( painter = painterResource(id = R.drawable.user), @@ -70,17 +37,22 @@ fun Profile(navController: NavController) { .clickable( enabled = true, onClick = { - navController?.navigate( - Screen.ProfileEdit.route.replace( - "{id}", - SingletonClass.getUserId().toString() - ) - ) + navController?.navigate(Screen.ProfileEdit.route.replace("{id}", LiveStore.getUserId().toString())) } ) ) Spacer (Modifier.padding(bottom = 10.dp)) Text("Открыть профиль", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + Spacer(Modifier.padding(bottom = 20.dp)) + Button( + modifier = Modifier + .fillMaxWidth() + .padding(all = 10.dp), + onClick = { + navController?.navigate(Screen.UserBooks.route.replace("{id}", LiveStore.getUserId().toString())) + }) { + Text(stringResource(id = R.string.my_books)) + } } } diff --git a/app/src/main/java/com/example/myapplication/composeui/Registration.kt b/app/src/main/java/com/example/myapplication/composeui/Registration.kt index 30d693e..a51dce9 100644 --- a/app/src/main/java/com/example/myapplication/composeui/Registration.kt +++ b/app/src/main/java/com/example/myapplication/composeui/Registration.kt @@ -5,8 +5,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface @@ -22,49 +28,83 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.rememberNavController import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.R -import com.example.myapplication.db.database.AppDatabase -import com.example.myapplication.db.model.User +import com.example.myapplication.composeui.ViewModel.UserDetails +import com.example.myapplication.composeui.ViewModel.UserEditViewModel +import com.example.myapplication.composeui.ViewModel.UserUiState +import com.example.myapplication.store.LiveStore import kotlinx.coroutines.launch +@Composable +fun Registration(navController: NavController, viewModel: UserEditViewModel = viewModel(factory = AppViewModelProvider.Factory)){ + val scope = rememberCoroutineScope() + Registration( + navController = navController, + userUiState = viewModel.userUiState, + onClick = { + scope.launch { + viewModel.saveUser() + navController?.navigate(Screen.Enter.route) + } + }, + onUpdate = viewModel::updateUiState + ) +} + @OptIn(ExperimentalMaterial3Api::class) @Composable -fun Registration(navController: NavController) { - val scope = rememberCoroutineScope() - val context = LocalContext.current +private fun Registration( + navController: NavController, + userUiState: UserUiState, + onClick: () -> Unit, + onUpdate: (UserDetails) -> Unit +) { + onUpdate(userUiState.userDetails.copy(role = "USER")) Column(Modifier.fillMaxWidth().padding(all = 40.dp)) { - var mail by remember{mutableStateOf("")} Text(stringResource(id = R.string.email)) OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = mail, onValueChange = { mail = it }, + value = userUiState.userDetails.email, onValueChange = { onUpdate(userUiState.userDetails.copy(email = it)) }, ) Spacer(Modifier.padding(bottom = 10.dp)) - var login by remember{mutableStateOf("")} Text(stringResource(id = R.string.login)) OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = login, onValueChange = { login = it }, + value = userUiState.userDetails.login, onValueChange = { onUpdate(userUiState.userDetails.copy(login = it)) }, ) Spacer(Modifier.padding(bottom = 10.dp)) - var password by remember{mutableStateOf("")} + var passwordVisible by rememberSaveable { mutableStateOf(false) } Text(stringResource(id = R.string.password)) OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = password, onValueChange = { password = it }, + value = userUiState.userDetails.password, onValueChange = { onUpdate(userUiState.userDetails.copy(password = it)) }, + visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + trailingIcon = { + val image = if (passwordVisible) + Icons.Filled.Close + else Icons.Filled.Check + + val description = if (passwordVisible) "Hide password" else "Show password" + + IconButton(onClick = { passwordVisible = !passwordVisible }) { + Icon(imageVector = image, description) + } + } ) Spacer(Modifier.padding(bottom = 20.dp)) Button( modifier = Modifier .fillMaxWidth() .padding(all = 10.dp), - onClick = { - scope.launch{ - //AppDatabase.getInstance(context).userDao().insert(User(0, login.toString(), password.toString(), mail.toString(), "USER")) - } - navController?.navigate(Screen.Enter.route) - }) { + onClick = onClick + ) { Text(stringResource(id = R.string.create_acc)) } } diff --git a/app/src/main/java/com/example/myapplication/composeui/UserBooks.kt b/app/src/main/java/com/example/myapplication/composeui/UserBooks.kt new file mode 100644 index 0000000..0bd59a1 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/composeui/UserBooks.kt @@ -0,0 +1,52 @@ +package com.example.myapplication.composeui + +import android.content.res.Configuration +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import com.example.myapplication.composeui.ViewModel.UserBooksViewModel +import com.example.myapplication.db.model.Book +import com.example.myapplication.ui.theme.MyApplicationTheme +import kotlinx.coroutines.launch + +@Composable +fun UserBooks(navController: NavController, userId: Int, viewModel: UserBooksViewModel = viewModel(factory = AppViewModelProvider.Factory)) { + val coroutineScope = rememberCoroutineScope() + val userBooksUiState = viewModel.userBooksUiState + val pagingBook: LazyPagingItems = viewModel.bookPagedData.collectAsLazyPagingItems() + LaunchedEffect(Unit) { + viewModel.refreshState() + } + fun refresh(){ + coroutineScope.launch { viewModel.refreshState() } + } + LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 180.dp)){ + items (pagingBook.itemCount){ + index -> BookCell(navController = navController, book = pagingBook[index]!!.copy()) + } + } +} + +@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun UserBooksPreview() { + MyApplicationTheme { + Surface( + color = MaterialTheme.colorScheme.background + ) { + UserBooks(navController = rememberNavController(), 0) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorBooksViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorBooksViewModel.kt new file mode 100644 index 0000000..1301fed --- /dev/null +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/AuthorBooksViewModel.kt @@ -0,0 +1,37 @@ +package com.example.myapplication.composeui.ViewModel + +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 androidx.paging.PagingData +import androidx.paging.filter +import com.example.myapplication.db.model.Book +import com.example.myapplication.db.respository.BookRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class AuthorBooksViewModel(savedStateHandle: SavedStateHandle, private val bookRepository: BookRepository) : ViewModel(){ + private val authorUid: Int = checkNotNull(savedStateHandle["id"]) + var authorBooksUiState by mutableStateOf(AuthorBooksUiState()) + private set + init { + viewModelScope.launch { + refreshState() + } + } + suspend fun refreshState() { + authorBooksUiState = AuthorBooksUiState(bookRepository.getByUserId(authorUid)) + } + + val bookPagedData: Flow> = bookRepository.loadAllBooksPaged().map{ + x -> x.filter{ + y-> (y.authorId == authorUid) + } + } +} + +data class AuthorBooksUiState(val bookList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/LoginViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/LoginViewModel.kt new file mode 100644 index 0000000..52fdc12 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/LoginViewModel.kt @@ -0,0 +1,51 @@ +package com.example.myapplication.composeui.ViewModel + +import android.annotation.SuppressLint +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.example.myapplication.db.model.User +import com.example.myapplication.db.respository.UserRepository +import com.example.myapplication.store.LiveStore +import com.example.myapplication.store.PreferencesStore + +class LoginViewModel ( + private val userRepository: UserRepository +) : ViewModel() { + var signInUserState by mutableStateOf(SignInUserState()) + private set + + fun updateState(signInUserDetails: SignInUserDetails) { + signInUserState = SignInUserState( + details = signInUserDetails + ) + } + + @SuppressLint("SuspiciousIndentation") + suspend fun signIn(context: Context): User? { + val user: User? = userRepository.tryLogin(signInUserState.details.login, signInUserState.details.password) + if(user != null) { + val store = PreferencesStore(context) + store.setUid(user.uid.toString()) + LiveStore.user.value = user.copy() + } + else{ + return null + } + signInUserState = SignInUserState( + details = signInUserState.details, + ) + return user + } +} + +data class SignInUserDetails( + val login: String = "", + val password: String = "" +) + +data class SignInUserState( + val details: SignInUserDetails = SignInUserDetails(), +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserBooksViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserBooksViewModel.kt new file mode 100644 index 0000000..134cf57 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserBooksViewModel.kt @@ -0,0 +1,37 @@ +package com.example.myapplication.composeui.ViewModel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.filter +import com.example.myapplication.db.model.Book +import com.example.myapplication.db.respository.BookRepository +import com.example.myapplication.store.LiveStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class UserBooksViewModel(private val bookRepository: BookRepository) : ViewModel(){ + private val userUid: Int = LiveStore.user.value?.uid ?: 0 + var userBooksUiState by mutableStateOf(UserBooksUiState()) + private set + init { + viewModelScope.launch { + refreshState() + } + } + suspend fun refreshState() { + userBooksUiState = UserBooksUiState(bookRepository.getByUserId(userUid)) + } + + val bookPagedData: Flow> = bookRepository.loadAllBooksPaged().map{ + x -> x.filter{ + y-> (y.userId == userUid) + } + } +} + +data class UserBooksUiState(val bookList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserEditViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserEditViewModel.kt index 80135b9..f8fcfb4 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserEditViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserEditViewModel.kt @@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModel import com.example.myapplication.db.respository.UserRepository import androidx.lifecycle.viewModelScope import com.example.myapplication.db.model.User +import com.example.myapplication.store.LiveStore import kotlinx.coroutines.launch class UserEditViewModel(savedStateHandle: SavedStateHandle, @@ -17,7 +18,7 @@ class UserEditViewModel(savedStateHandle: SavedStateHandle, var userUiState by mutableStateOf(UserUiState()) private set - private val userUid: Int = checkNotNull(savedStateHandle["id"]) + private val userUid: Int = LiveStore.user.value?.uid ?: 0 init { viewModelScope.launch { if (userUid > 0) { diff --git a/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserPageViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserPageViewModel.kt index 796da85..3ed93a5 100644 --- a/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserPageViewModel.kt +++ b/app/src/main/java/com/example/myapplication/composeui/ViewModel/UserPageViewModel.kt @@ -9,21 +9,21 @@ import androidx.lifecycle.SavedStateHandle import com.example.myapplication.db.model.User import kotlinx.coroutines.launch import com.example.myapplication.db.respository.UserRepository +import com.example.myapplication.store.LiveStore class UserPageViewModel(savedStateHandle: SavedStateHandle, private val userRepository: UserRepository) : ViewModel(){ - private val userId: Int = checkNotNull(savedStateHandle["id"]) var userPageUiState by mutableStateOf(UserPageUiState()) private set init { viewModelScope.launch { - if (userId > 0) { + if (LiveStore.user.value != null) { refreshState() } } } suspend fun refreshState() { - userPageUiState = UserPageUiState(userRepository.getByUid(userId)) + userPageUiState = UserPageUiState(LiveStore.user.value) } } diff --git a/app/src/main/java/com/example/myapplication/composeui/authenticator/Authenticator.kt b/app/src/main/java/com/example/myapplication/composeui/authenticator/Authenticator.kt new file mode 100644 index 0000000..74508eb --- /dev/null +++ b/app/src/main/java/com/example/myapplication/composeui/authenticator/Authenticator.kt @@ -0,0 +1,38 @@ +package com.example.myapplication.composeui.authenticator + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.myapplication.store.LiveStore +import com.example.myapplication.store.PreferencesStore +import com.example.myapplication.composeui.AppViewModelProvider +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +@Composable +fun Authenticator( + viewModel: AuthenticatorViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val context = LocalContext.current + val store = PreferencesStore(context) + val scope = rememberCoroutineScope() + val uid = store.getUid().collectAsState(initial = "").value + + fun synchronize() { + + scope.launch { + if (uid == "") { + LiveStore.user.value = null + return@launch + } + try { + viewModel.findUserByUid(uid.toInt()) + } + catch (_: Exception){} + } + } + + synchronize() +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/authenticator/AuthenticatorViewModel.kt b/app/src/main/java/com/example/myapplication/composeui/authenticator/AuthenticatorViewModel.kt new file mode 100644 index 0000000..62e4772 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/composeui/authenticator/AuthenticatorViewModel.kt @@ -0,0 +1,21 @@ +package com.example.myapplication.composeui.authenticator + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.example.myapplication.db.model.User +import com.example.myapplication.db.respository.UserRepository + +class AuthenticatorViewModel( + private val userRepository: UserRepository +) : ViewModel() { + var authUiState by mutableStateOf(AuthenticatorUiState()) + private set + + suspend fun findUserByUid(uid: Int) { + authUiState = AuthenticatorUiState(userRepository.getByUid(uid)) + } +} + +data class AuthenticatorUiState(val user: User? = null) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/navigation/MainNavbar.kt b/app/src/main/java/com/example/myapplication/composeui/navigation/MainNavbar.kt index dfddcb0..d6d74a8 100644 --- a/app/src/main/java/com/example/myapplication/composeui/navigation/MainNavbar.kt +++ b/app/src/main/java/com/example/myapplication/composeui/navigation/MainNavbar.kt @@ -36,6 +36,7 @@ import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.R +import com.example.myapplication.composeui.AuthorBooks import com.example.myapplication.composeui.Main import com.example.myapplication.composeui.Registration import com.example.myapplication.composeui.Enter @@ -49,6 +50,7 @@ import com.example.myapplication.composeui.BookEdit import com.example.myapplication.composeui.BookSearch import com.example.myapplication.composeui.ListAuthors import com.example.myapplication.composeui.AuthorEdit +import com.example.myapplication.composeui.UserBooks @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -163,6 +165,18 @@ fun Navhost( ) { backStackEntry -> backStackEntry.arguments?.let { BookSearch(navController = navController ,it.getString ("searchStr", "")) } } + composable( + Screen.AuthorBooks.route, + arguments = listOf(navArgument("id") { type = NavType.IntType }) + ) { backStackEntry -> + backStackEntry.arguments?.let { AuthorBooks(navController = navController, it.getInt("id")) } + } + composable( + Screen.UserBooks.route, + arguments = listOf(navArgument("id") { type = NavType.IntType }) + ) { backStackEntry -> + backStackEntry.arguments?.let { UserBooks(navController = navController, it.getInt("id")) } + } } } diff --git a/app/src/main/java/com/example/myapplication/composeui/navigation/Screen.kt b/app/src/main/java/com/example/myapplication/composeui/navigation/Screen.kt index bb09b80..7190e7f 100644 --- a/app/src/main/java/com/example/myapplication/composeui/navigation/Screen.kt +++ b/app/src/main/java/com/example/myapplication/composeui/navigation/Screen.kt @@ -46,6 +46,12 @@ enum class Screen( BookSearch( "book-search/{searchStr}", R.string.search, showInBottomBar = false ), + AuthorBooks( + "author-books/{id}", R.string.authors_books, showInBottomBar = false + ), + UserBooks( + "user-books/{id}", R.string.my_books, showInBottomBar = false + ), BookView( "book-view/{id}", R.string.book_view_title, showInBottomBar = false ), diff --git a/app/src/main/java/com/example/myapplication/store/LiveStore.kt b/app/src/main/java/com/example/myapplication/store/LiveStore.kt new file mode 100644 index 0000000..9d4d940 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/store/LiveStore.kt @@ -0,0 +1,16 @@ +package com.example.myapplication.store + +import androidx.lifecycle.MutableLiveData +import com.example.myapplication.db.model.User + +class LiveStore{ + companion object{ + val user = MutableLiveData(null) + fun getUserId(): Int{ + return user.value?.uid ?: 0 + } + fun getRole(): String{ + return user.value?.role ?: "USER" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/store/PreferencesStore.kt b/app/src/main/java/com/example/myapplication/store/PreferencesStore.kt new file mode 100644 index 0000000..0f22974 --- /dev/null +++ b/app/src/main/java/com/example/myapplication/store/PreferencesStore.kt @@ -0,0 +1,34 @@ +package com.example.myapplication.store + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class PreferencesStore(private val context: Context) { + companion object { + private val Context.dataStore: DataStore by preferencesDataStore("Store") + val UID = stringPreferencesKey("uid") + } + + fun getUid(): Flow { + return context.dataStore.data + .map { preferences -> + preferences[UID] ?: "" + } + } + + private suspend fun saveStringValue(key: Preferences.Key, value: String) { + context.dataStore.edit { preferences -> + preferences[key] = value + } + } + + suspend fun setUid(uid: String) { + saveStringValue(UID, uid) + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c081f8f..483a438 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,21 +12,14 @@ Писатели Писатель Книга + Книги + Мои книги + Книги автора Войти Создать аккаунт Почта Логин Пароль - -

Пользователь

\n -

User1

\n\n -

Дата регистрации

\n -

20.09.2023

\n\n -

Прочитано книг

\n -

7

\n\n -

Загружено книг

\n -

0

-
Название Автор Содержание @@ -37,5 +30,4 @@ Загрузить Добавить Читать - Вы можете помочь нашей библиотеке, загрузив свою книгу. \ No newline at end of file diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 1a66794..53d4a5a 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -3,6 +3,6 @@ 100.87.48.148 192.168.43.198 - 89.239.172.45 + 89.239.174.67 \ No newline at end of file