Регистрация, аутентификация, вывод книг по автору и по пользователю

This commit is contained in:
ityurner02@mail.ru 2023-12-24 20:16:50 +04:00
parent 3b17a15278
commit ef72894515
28 changed files with 527 additions and 129 deletions

View File

@ -1,11 +1,11 @@
{ {
"users": [ "users": [
{ {
"id": 1,
"login": "Admin", "login": "Admin",
"password": "Admin", "password": "Admin",
"email": "admin@mail.ru", "email": "admin@mail.ru",
"role": "ADMIN", "role": "ADMIN"
"id": 1
}, },
{ {
"id": 2, "id": 2,
@ -13,6 +13,13 @@
"password": "123", "password": "123",
"email": "user1@mail.ru", "email": "user1@mail.ru",
"role": "USER" "role": "USER"
},
{
"id": 3,
"login": "User2",
"password": "123",
"email": "user2@gmail.ru",
"role": "USER"
} }
], ],
"authors": [ "authors": [
@ -3957,7 +3964,7 @@
-39 -39
], ],
"authorId": 2, "authorId": 2,
"userId": 2 "userId": 1
}, },
{ {
"title": "Вельд", "title": "Вельд",
@ -7316,7 +7323,7 @@
-39 -39
], ],
"authorId": 2, "authorId": 2,
"userId": 2, "userId": 1,
"id": 4 "id": 4
}, },
{ {

View File

@ -89,6 +89,7 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") 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.compose.runtime:runtime-livedata:1.5.4")
implementation("androidx.datastore:datastore-preferences:1.0.0")
} }

View File

@ -11,6 +11,7 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview 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.composeui.navigation.MainNavbar
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
@ -24,7 +25,8 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
MainNavbar() Authenticator()
MainNavbar();
} }
} }
} }

View File

@ -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
}
}

View File

@ -100,7 +100,7 @@ interface ServerService{
suspend fun getUserByLoginPass( suspend fun getUserByLoginPass(
@Query("login") login: String, @Query("login") login: String,
@Query("password") password: String @Query("password") password: String
): UserRemote ): List<UserRemote>
@POST("users") @POST("users")
suspend fun createUser( suspend fun createUser(
@Body user: UserRemote, @Body user: UserRemote,
@ -117,7 +117,7 @@ interface ServerService{
@Path("id") id: Int, @Path("id") id: Int,
): UserRemote ): UserRemote
companion object { 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 @Volatile
private var INSTANCE: ServerService? = null private var INSTANCE: ServerService? = null

View File

@ -28,7 +28,7 @@ class RestUserRepository(
} }
override suspend fun tryLogin(login: String, password: String): User? { 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) { override suspend fun insert(user: User) {

View File

@ -6,15 +6,19 @@ import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.myapplication.MyApplication 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.AuthorDropDownViewModel
import com.example.myapplication.composeui.ViewModel.AuthorEditViewModel import com.example.myapplication.composeui.ViewModel.AuthorEditViewModel
import com.example.myapplication.composeui.ViewModel.AuthorListViewModel import com.example.myapplication.composeui.ViewModel.AuthorListViewModel
import com.example.myapplication.composeui.ViewModel.BookEditViewModel import com.example.myapplication.composeui.ViewModel.BookEditViewModel
import com.example.myapplication.composeui.ViewModel.BookListViewModel import com.example.myapplication.composeui.ViewModel.BookListViewModel
import com.example.myapplication.composeui.ViewModel.BookPageViewModel 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.SearchPageViewModel
import com.example.myapplication.composeui.ViewModel.UserBooksViewModel
import com.example.myapplication.composeui.ViewModel.UserEditViewModel import com.example.myapplication.composeui.ViewModel.UserEditViewModel
import com.example.myapplication.composeui.ViewModel.UserPageViewModel import com.example.myapplication.composeui.ViewModel.UserPageViewModel
import com.example.myapplication.composeui.authenticator.AuthenticatorViewModel
object AppViewModelProvider { object AppViewModelProvider {
val Factory = viewModelFactory { val Factory = viewModelFactory {
@ -69,6 +73,21 @@ object AppViewModelProvider {
myApplication().container.authorRestRepository 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)
}
} }
} }

View File

@ -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)
}
}
}

View File

@ -27,14 +27,17 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.myapplication.MainActivity import com.example.myapplication.MainActivity
import com.example.myapplication.R
import com.example.myapplication.composeui.ViewModel.AuthorListViewModel import com.example.myapplication.composeui.ViewModel.AuthorListViewModel
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.db.database.AppDatabase import com.example.myapplication.db.database.AppDatabase
import com.example.myapplication.db.model.Author import com.example.myapplication.db.model.Author
import com.example.myapplication.store.LiveStore
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -46,7 +49,7 @@ fun AuthorCell(navController: NavController?, author: Author, viewModel: AuthorL
val context = LocalContext.current val context = LocalContext.current
Column(modifier = Modifier Column(modifier = Modifier
.padding(all = 5.dp) .padding(all = 5.dp)
.requiredSize(170.dp, 100.dp) .requiredSize(170.dp, 150.dp)
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.border(1.dp, Color.LightGray, shape = 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))
}
} }
} }

View File

@ -33,7 +33,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.SingletonClass
import com.example.myapplication.composeui.ViewModel.AuthorsListUiState import com.example.myapplication.composeui.ViewModel.AuthorsListUiState
import com.example.myapplication.composeui.ViewModel.AuthorsUiState import com.example.myapplication.composeui.ViewModel.AuthorsUiState
import com.example.myapplication.composeui.ViewModel.AuthorDetails import com.example.myapplication.composeui.ViewModel.AuthorDetails

View File

@ -8,15 +8,12 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
@ -33,7 +30,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.SingletonClass
import com.example.myapplication.composeui.ViewModel.AuthorDropDownViewModel import com.example.myapplication.composeui.ViewModel.AuthorDropDownViewModel
import com.example.myapplication.composeui.ViewModel.AuthorsListUiState import com.example.myapplication.composeui.ViewModel.AuthorsListUiState
import com.example.myapplication.composeui.ViewModel.AuthorsUiState 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.BookUiState
import com.example.myapplication.composeui.ViewModel.ImageUploader import com.example.myapplication.composeui.ViewModel.ImageUploader
import com.example.myapplication.db.model.Author import com.example.myapplication.db.model.Author
import com.example.myapplication.store.LiveStore
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
@ -124,8 +121,7 @@ private fun BookEdit(
) )
) )
} }
var SingletonClass = SingletonClass() onUpdate(bookUiState.bookDetails.copy(userId = LiveStore.getUserId()))
onUpdate(bookUiState.bookDetails.copy(userId = SingletonClass.getUserId()))
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()

View File

@ -6,14 +6,19 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview 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.setValue
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext 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 androidx.navigation.compose.rememberNavController
import com.example.myapplication.MainActivity import com.example.myapplication.MainActivity
import com.example.myapplication.SingletonClass import com.example.myapplication.composeui.ViewModel.LoginViewModel
import com.example.myapplication.api.ServerService
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.db.database.AppDatabase import com.example.myapplication.store.PreferencesStore
import com.example.myapplication.db.model.User
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.GlobalScope
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Enter(navController: NavController) { fun Enter(navController: NavController, viewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
var SingletonClass = SingletonClass()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
val service: ServerService val user = viewModel.signInUserState
var login by remember{mutableStateOf("")}
var password by remember{mutableStateOf("")}
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = 40.dp)) { .padding(all = 40.dp)) {
Text(stringResource(id = R.string.login)) Text(stringResource(id = R.string.login))
OutlinedTextField(modifier = Modifier.fillMaxWidth(), 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)) Spacer(Modifier.padding(bottom = 10.dp))
Text(stringResource(id = R.string.password)) Text(stringResource(id = R.string.password))
var passwordVisible by rememberSaveable { mutableStateOf(false) }
OutlinedTextField(modifier = Modifier.fillMaxWidth(), 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)) Spacer(Modifier.padding(bottom = 20.dp))
Button( Button(
@ -68,15 +84,13 @@ fun Enter(navController: NavController) {
.padding(all = 10.dp), .padding(all = 10.dp),
onClick = { onClick = {
scope.launch { scope.launch {
//if( AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString()) != null){ if(viewModel.signIn(context) != null) {
// SingletonClass.setUserId(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.uid!!) navController?.navigate(Screen.Profile.route)
//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)
//else{ toast.show()
// val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT) }
// toast.show()
//}
} }
}) { }) {
Text(stringResource(id = R.string.enter)) Text(stringResource(id = R.string.enter))

View File

@ -1,66 +1,33 @@
package com.example.myapplication.composeui package com.example.myapplication.composeui
import android.annotation.SuppressLint
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.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.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable 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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource 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.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.myapplication.composeui.navigation.Screen 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.R
import com.example.myapplication.SingletonClass import com.example.myapplication.store.LiveStore
import com.example.myapplication.composeui.ViewModel.UserPageViewModel
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable @Composable
fun Profile(navController: NavController) { fun Profile(navController: NavController) {
var SingletonClass = SingletonClass()
Column(Modifier.padding(all = 40.dp)) { Column(Modifier.padding(all = 40.dp)) {
Image( Image(
painter = painterResource(id = R.drawable.user), painter = painterResource(id = R.drawable.user),
@ -70,17 +37,22 @@ fun Profile(navController: NavController) {
.clickable( .clickable(
enabled = true, enabled = true,
onClick = { onClick = {
navController?.navigate( navController?.navigate(Screen.ProfileEdit.route.replace("{id}", LiveStore.getUserId().toString()))
Screen.ProfileEdit.route.replace(
"{id}",
SingletonClass.getUserId().toString()
)
)
} }
) )
) )
Spacer (Modifier.padding(bottom = 10.dp)) Spacer (Modifier.padding(bottom = 10.dp))
Text("Открыть профиль", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) 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))
}
} }
} }

View File

@ -5,8 +5,14 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.ExperimentalMaterial3Api
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@ -22,49 +28,83 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext 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 androidx.navigation.compose.rememberNavController
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.db.database.AppDatabase import com.example.myapplication.composeui.ViewModel.UserDetails
import com.example.myapplication.db.model.User import com.example.myapplication.composeui.ViewModel.UserEditViewModel
import com.example.myapplication.composeui.ViewModel.UserUiState
import com.example.myapplication.store.LiveStore
import kotlinx.coroutines.launch 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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Registration(navController: NavController) { private fun Registration(
val scope = rememberCoroutineScope() navController: NavController,
val context = LocalContext.current userUiState: UserUiState,
onClick: () -> Unit,
onUpdate: (UserDetails) -> Unit
) {
onUpdate(userUiState.userDetails.copy(role = "USER"))
Column(Modifier.fillMaxWidth().padding(all = 40.dp)) { Column(Modifier.fillMaxWidth().padding(all = 40.dp)) {
var mail by remember{mutableStateOf("")}
Text(stringResource(id = R.string.email)) Text(stringResource(id = R.string.email))
OutlinedTextField(modifier = Modifier.fillMaxWidth(), 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)) Spacer(Modifier.padding(bottom = 10.dp))
var login by remember{mutableStateOf("")}
Text(stringResource(id = R.string.login)) Text(stringResource(id = R.string.login))
OutlinedTextField(modifier = Modifier.fillMaxWidth(), 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)) Spacer(Modifier.padding(bottom = 10.dp))
var password by remember{mutableStateOf("")} var passwordVisible by rememberSaveable { mutableStateOf(false) }
Text(stringResource(id = R.string.password)) Text(stringResource(id = R.string.password))
OutlinedTextField(modifier = Modifier.fillMaxWidth(), 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)) Spacer(Modifier.padding(bottom = 20.dp))
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp), .padding(all = 10.dp),
onClick = { onClick = onClick
scope.launch{ ) {
//AppDatabase.getInstance(context).userDao().insert(User(0, login.toString(), password.toString(), mail.toString(), "USER"))
}
navController?.navigate(Screen.Enter.route)
}) {
Text(stringResource(id = R.string.create_acc)) Text(stringResource(id = R.string.create_acc))
} }
} }

View File

@ -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)
}
}
}

View File

@ -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())

View File

@ -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(),
)

View File

@ -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())

View File

@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModel
import com.example.myapplication.db.respository.UserRepository import com.example.myapplication.db.respository.UserRepository
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.myapplication.db.model.User import com.example.myapplication.db.model.User
import com.example.myapplication.store.LiveStore
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class UserEditViewModel(savedStateHandle: SavedStateHandle, class UserEditViewModel(savedStateHandle: SavedStateHandle,
@ -17,7 +18,7 @@ class UserEditViewModel(savedStateHandle: SavedStateHandle,
var userUiState by mutableStateOf(UserUiState()) var userUiState by mutableStateOf(UserUiState())
private set private set
private val userUid: Int = checkNotNull(savedStateHandle["id"]) private val userUid: Int = LiveStore.user.value?.uid ?: 0
init { init {
viewModelScope.launch { viewModelScope.launch {
if (userUid > 0) { if (userUid > 0) {

View File

@ -9,21 +9,21 @@ import androidx.lifecycle.SavedStateHandle
import com.example.myapplication.db.model.User import com.example.myapplication.db.model.User
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.example.myapplication.db.respository.UserRepository import com.example.myapplication.db.respository.UserRepository
import com.example.myapplication.store.LiveStore
class UserPageViewModel(savedStateHandle: SavedStateHandle, private val userRepository: UserRepository) : ViewModel(){ class UserPageViewModel(savedStateHandle: SavedStateHandle, private val userRepository: UserRepository) : ViewModel(){
private val userId: Int = checkNotNull(savedStateHandle["id"])
var userPageUiState by mutableStateOf(UserPageUiState()) var userPageUiState by mutableStateOf(UserPageUiState())
private set private set
init { init {
viewModelScope.launch { viewModelScope.launch {
if (userId > 0) { if (LiveStore.user.value != null) {
refreshState() refreshState()
} }
} }
} }
suspend fun refreshState() { suspend fun refreshState() {
userPageUiState = UserPageUiState(userRepository.getByUid(userId)) userPageUiState = UserPageUiState(LiveStore.user.value)
} }
} }

View File

@ -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()
}

View File

@ -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)

View File

@ -36,6 +36,7 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.composeui.AuthorBooks
import com.example.myapplication.composeui.Main import com.example.myapplication.composeui.Main
import com.example.myapplication.composeui.Registration import com.example.myapplication.composeui.Registration
import com.example.myapplication.composeui.Enter 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.BookSearch
import com.example.myapplication.composeui.ListAuthors import com.example.myapplication.composeui.ListAuthors
import com.example.myapplication.composeui.AuthorEdit import com.example.myapplication.composeui.AuthorEdit
import com.example.myapplication.composeui.UserBooks
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -163,6 +165,18 @@ fun Navhost(
) { backStackEntry -> ) { backStackEntry ->
backStackEntry.arguments?.let { BookSearch(navController = navController ,it.getString ("searchStr", "")) } 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")) }
}
} }
} }

View File

@ -46,6 +46,12 @@ enum class Screen(
BookSearch( BookSearch(
"book-search/{searchStr}", R.string.search, showInBottomBar = false "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( BookView(
"book-view/{id}", R.string.book_view_title, showInBottomBar = false "book-view/{id}", R.string.book_view_title, showInBottomBar = false
), ),

View File

@ -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"
}
}
}

View File

@ -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)
}
}

View File

@ -12,21 +12,14 @@
<string name="listauthors">Писатели</string> <string name="listauthors">Писатели</string>
<string name="author">Писатель</string> <string name="author">Писатель</string>
<string name="book_view_title">Книга</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="enter">Войти</string>
<string name="create_acc">Создать аккаунт</string> <string name="create_acc">Создать аккаунт</string>
<string name="email">Почта</string> <string name="email">Почта</string>
<string name="login">Логин</string> <string name="login">Логин</string>
<string name="password">Пароль</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="book_title">Название</string>
<string name="author_name">Автор</string> <string name="author_name">Автор</string>
<string name="description">Содержание</string> <string name="description">Содержание</string>
@ -37,5 +30,4 @@
<string name="load_book">Загрузить</string> <string name="load_book">Загрузить</string>
<string name="add_book">Добавить</string> <string name="add_book">Добавить</string>
<string name="read_book">Читать</string> <string name="read_book">Читать</string>
<string name="help_me_pls">Вы можете помочь нашей библиотеке, загрузив свою книгу.</string>
</resources> </resources>

View File

@ -3,6 +3,6 @@
<domain-config cleartextTrafficPermitted="true"> <domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">100.87.48.148</domain> <domain includeSubdomains="true">100.87.48.148</domain>
<domain includeSubdomains="true">192.168.43.198</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> </domain-config>
</network-security-config> </network-security-config>