Регистрация, аутентификация, вывод книг по автору и по пользователю
This commit is contained in:
parent
3b17a15278
commit
ef72894515
@ -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
|
||||
},
|
||||
{
|
||||
|
@ -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")
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -100,7 +100,7 @@ interface ServerService{
|
||||
suspend fun getUserByLoginPass(
|
||||
@Query("login") login: String,
|
||||
@Query("password") password: String
|
||||
): UserRemote
|
||||
): List<UserRemote>
|
||||
@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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Book> = 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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!!)
|
||||
if(viewModel.signIn(context) != null) {
|
||||
navController?.navigate(Screen.Profile.route)
|
||||
//}
|
||||
//else{
|
||||
// val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT)
|
||||
// toast.show()
|
||||
//}
|
||||
}
|
||||
else{
|
||||
val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT)
|
||||
toast.show()
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.enter))
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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<Book> = 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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<PagingData<Book>> = bookRepository.loadAllBooksPaged().map{
|
||||
x -> x.filter{
|
||||
y-> (y.authorId == authorUid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class AuthorBooksUiState(val bookList: List<Book> = listOf())
|
@ -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(),
|
||||
)
|
@ -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<PagingData<Book>> = bookRepository.loadAllBooksPaged().map{
|
||||
x -> x.filter{
|
||||
y-> (y.userId == userUid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UserBooksUiState(val bookList: List<Book> = listOf())
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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)
|
@ -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")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
),
|
||||
|
@ -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<User?>(null)
|
||||
fun getUserId(): Int{
|
||||
return user.value?.uid ?: 0
|
||||
}
|
||||
fun getRole(): String{
|
||||
return user.value?.role ?: "USER"
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Preferences> by preferencesDataStore("Store")
|
||||
val UID = stringPreferencesKey("uid")
|
||||
}
|
||||
|
||||
fun getUid(): Flow<String> {
|
||||
return context.dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[UID] ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveStringValue(key: Preferences.Key<String>, value: String) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setUid(uid: String) {
|
||||
saveStringValue(UID, uid)
|
||||
}
|
||||
}
|
@ -12,21 +12,14 @@
|
||||
<string name="listauthors">Писатели</string>
|
||||
<string name="author">Писатель</string>
|
||||
<string name="book_view_title">Книга</string>
|
||||
<string name="books">Книги</string>
|
||||
<string name="my_books">Мои книги</string>
|
||||
<string name="authors_books">Книги автора</string>
|
||||
<string name="enter">Войти</string>
|
||||
<string name="create_acc">Создать аккаунт</string>
|
||||
<string name="email">Почта</string>
|
||||
<string name="login">Логин</string>
|
||||
<string name="password">Пароль</string>
|
||||
<string name="stats">
|
||||
<p>Пользователь</p>\n
|
||||
<p>User1</p>\n\n
|
||||
<p>Дата регистрации</p>\n
|
||||
<p>20.09.2023</p>\n\n
|
||||
<p>Прочитано книг</p>\n
|
||||
<p>7</p>\n\n
|
||||
<p>Загружено книг</p>\n
|
||||
<p>0</p>
|
||||
</string>
|
||||
<string name="book_title">Название</string>
|
||||
<string name="author_name">Автор</string>
|
||||
<string name="description">Содержание</string>
|
||||
@ -37,5 +30,4 @@
|
||||
<string name="load_book">Загрузить</string>
|
||||
<string name="add_book">Добавить</string>
|
||||
<string name="read_book">Читать</string>
|
||||
<string name="help_me_pls">Вы можете помочь нашей библиотеке, загрузив свою книгу.</string>
|
||||
</resources>
|
@ -3,6 +3,6 @@
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">100.87.48.148</domain>
|
||||
<domain includeSubdomains="true">192.168.43.198</domain>
|
||||
<domain includeSubdomains="true">89.239.172.45</domain>
|
||||
<domain includeSubdomains="true">89.239.174.67</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
Loading…
Reference in New Issue
Block a user