This commit is contained in:
ityurner02@mail.ru 2023-12-13 18:53:28 +04:00
parent 62619bd13d
commit d5980d6ff2
59 changed files with 1834 additions and 554 deletions

View File

@ -37,5 +37,6 @@
<option name="composableFile" value="true" /> <option name="composableFile" value="true" />
<option name="previewFile" value="true" /> <option name="previewFile" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="UnusedEquals" enabled="false" level="WARNING" enabled_by_default="false" />
</profile> </profile>
</component> </component>

View File

@ -73,4 +73,9 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-test-manifest")
// Paging
val paging_version = "3.2.0-rc01"
implementation("androidx.paging:paging-runtime:$paging_version")
implementation("androidx.paging:paging-compose:$paging_version")
} }

View File

@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<application <application
android:name=".MyApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
app/src/main/assets/img.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -1,5 +1,6 @@
package com.example.myapplication package com.example.myapplication
import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@ -16,6 +17,7 @@ import com.example.myapplication.ui.theme.MyApplicationTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
MainActivity.appContext = applicationContext
setContent { setContent {
MyApplicationTheme { MyApplicationTheme {
Surface( Surface(
@ -27,6 +29,11 @@ class MainActivity : ComponentActivity() {
} }
} }
} }
companion object {
lateinit var appContext: Context
}
} }
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)

View File

@ -0,0 +1,14 @@
package com.example.myapplication
import android.app.Application
import com.example.myapplication.db.database.AppContainer
import com.example.myapplication.db.database.AppDataContainer
class MyApplication : Application() {
lateinit var container: AppContainer
override fun onCreate() {
super.onCreate()
container = AppDataContainer(this)
}
}

View File

@ -0,0 +1,14 @@
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

@ -0,0 +1,76 @@
package com.example.myapplication.composeui
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
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.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.SearchPageViewModel
import com.example.myapplication.composeui.ViewModel.UserEditViewModel
import com.example.myapplication.composeui.ViewModel.UserPageViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
UserPageViewModel(
this.createSavedStateHandle(),
myApplication().container.userRepository
)
}
initializer {
UserEditViewModel(
this.createSavedStateHandle(),
myApplication().container.userRepository
)
}
initializer {
SearchPageViewModel(
myApplication().container.bookRepository,
this.createSavedStateHandle()
)
}
initializer {
BookPageViewModel(
myApplication().container.bookRepository,
this.createSavedStateHandle()
)
}
initializer {
BookListViewModel(
myApplication().container.bookRepository
)
}
initializer {
BookEditViewModel(
this.createSavedStateHandle(),
myApplication().container.bookRepository
)
}
initializer {
AuthorListViewModel(
myApplication().container.authorRepository
)
}
initializer {
AuthorEditViewModel(
this.createSavedStateHandle(),
myApplication().container.authorRepository
)
}
initializer {
AuthorDropDownViewModel(
myApplication().container.authorRepository
)
}
}
}
fun CreationExtras.myApplication(): MyApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MyApplication)

View File

@ -0,0 +1,97 @@
package com.example.myapplication.composeui
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.MainActivity
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 kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.lang.Exception
@Composable
fun AuthorCell(navController: NavController?, author: Author, viewModel: AuthorListViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val scope = rememberCoroutineScope()
val context = LocalContext.current
Column(modifier = Modifier
.padding(all = 5.dp)
.requiredSize(170.dp, 100.dp)
.clip(RoundedCornerShape(10.dp))
.border(1.dp, Color.LightGray, shape = RoundedCornerShape(10.dp))
) {
Text("${author.name}")
Row{
Button(
modifier = Modifier
.padding(all = 5.dp),
onClick = {
val route = Screen.AuthorEdit.route.replace("{id}", author.uid.toString())
navController!!.navigate(route)
},
) {
Icon(
imageVector = Icons.Default.Create,
contentDescription = "Изменить",
tint = Color.White
)
}
Button(
modifier = Modifier
.padding(all = 5.dp),
onClick = {
scope.launch {
if (AppDatabase.getInstance(context).bookDao().getByAuthorId(author.uid).firstOrNull().toString() == "[]") {
viewModel.deleteAuthor(author)
} else {
val toast = Toast.makeText(
MainActivity.appContext,
"Невозможно удалить, есть книги данного автора",
Toast.LENGTH_SHORT
)
toast.show()
}
}
},
colors = ButtonDefaults.buttonColors(containerColor = Color.Red),
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Удалить",
tint = Color.White
)
}
}
}
}

View File

@ -0,0 +1,90 @@
package com.example.myapplication.composeui
import android.graphics.Bitmap
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
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.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
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.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
import com.example.myapplication.composeui.ViewModel.AuthorEditViewModel
import com.example.myapplication.composeui.ViewModel.AuthorUiState
import com.example.myapplication.composeui.ViewModel.ImageUploader
import com.example.myapplication.db.model.Author
import kotlinx.coroutines.launch
@Composable
fun AuthorEdit(navController: NavController, viewModel: AuthorEditViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val coroutineScope = rememberCoroutineScope()
AuthorEdit(
authorUiState = viewModel.authorUiState,
onClick = {
coroutineScope.launch {
viewModel.saveAuthor()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AuthorEdit(
authorUiState: AuthorUiState,
onClick: () -> Unit,
onUpdate: (AuthorDetails) -> Unit,
) {
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
.verticalScroll(ScrollState(1))
) {
Spacer(modifier = Modifier.size(10.dp))
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = authorUiState.authorDetails.name,
onValueChange = { onUpdate(authorUiState.authorDetails.copy(name = it)) },
label = { Text("Имя автора") },
singleLine = true
)
Button(
onClick = onClick,
enabled = authorUiState.isEntryValid,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Сохранить")
}
}
}

View File

@ -0,0 +1,112 @@
package com.example.myapplication.composeui
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.composeui.ViewModel.BookListViewModel
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.db.model.Book
import kotlinx.coroutines.launch
@Composable
fun BookCell(navController: NavController?, book: Book, viewModel: BookListViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val scope = rememberCoroutineScope()
Column(modifier = Modifier
.padding(all = 5.dp)
.requiredSize(170.dp, 300.dp)
.clip(RoundedCornerShape(10.dp))
.border(1.dp, Color.LightGray, shape = RoundedCornerShape(10.dp))
.clickable(onClick = {
navController?.navigate(
Screen.BookView.route.replace(
"{id}",
book.uid.toString()
)
)
})
) {
Button(modifier = Modifier
.fillMaxWidth(),
shape = RoundedCornerShape(0.dp),
contentPadding = PaddingValues(0.dp),
onClick = {
navController?.navigate(
Screen.BookView.route.replace(
"{id}",
book.uid.toString()
)
)
}) {
Image(
bitmap = book.cover!!.asImageBitmap(),
contentDescription = book.title,
contentScale = ContentScale.Crop,
modifier = Modifier
.requiredSize(170.dp)
.clip(RoundedCornerShape(0.dp))
)
}
Column(modifier = Modifier.padding(all=5.dp)) {
Text("${book.title}")
Row{
Button(
modifier = Modifier
.padding(all = 5.dp),
onClick = {
val route = Screen.BookEdit.route.replace("{id}", book.uid.toString())
navController!!.navigate(route)
},
) {
Icon(
imageVector = Icons.Default.Create,
contentDescription = "Изменить",
tint = Color.White
)
}
Button(
modifier = Modifier
.padding(all = 5.dp),
onClick = {
scope.launch {
viewModel.deleteBook(book)
}
},
colors = ButtonDefaults.buttonColors(containerColor = Color.Red),
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Удалить",
tint = Color.White
)
}
}
}
}
}

View File

@ -0,0 +1,173 @@
package com.example.myapplication.composeui
import android.graphics.Bitmap
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
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.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
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.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
import com.example.myapplication.composeui.ViewModel.BookDetails
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 kotlinx.coroutines.launch
@Composable
fun BookEdit(navController: NavController, viewModel: BookEditViewModel = viewModel(factory = AppViewModelProvider.Factory), authorViewModel: AuthorDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val coroutineScope = rememberCoroutineScope()
authorViewModel.setCurrentAuthor(viewModel.bookUiState.bookDetails.authorId)
BookEdit(
bookUiState = viewModel.bookUiState,
authorUiState = authorViewModel.authorsUiState,
onClick = {
coroutineScope.launch {
viewModel.saveBook()
navController.popBackStack()
}
},
authorsListUiState = authorViewModel.authorsListUiState,
onUpdate = viewModel::updateUiState,
onAuthorUpdate = authorViewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AuthorDropDown(
authorUiState: AuthorsUiState,
authorsListUiState: AuthorsListUiState,
onAuthorUpdate: (Author) -> Unit
) {
var expanded: Boolean by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = Modifier
.padding(top = 7.dp),
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
value =authorUiState.author?.name ?: stringResource(id = R.string.author_name),
onValueChange = {},
readOnly = true,
modifier = Modifier
.fillMaxWidth()
.menuAnchor()
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(Color.White)
.exposedDropdownSize()
) {
authorsListUiState.authorList.forEach { author ->
DropdownMenuItem(
text = {
Text(text = author.name)
},
onClick = {
onAuthorUpdate(author)
expanded = false
}
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun BookEdit(
bookUiState: BookUiState,
authorUiState: AuthorsUiState,
authorsListUiState: AuthorsListUiState,
onClick: () -> Unit,
onUpdate: (BookDetails) -> Unit,
onAuthorUpdate: (Author) -> Unit
) {
fun handleImageUpload(bitmap: Bitmap) {
onUpdate(
bookUiState.bookDetails.copy(
cover = bitmap
)
)
}
var SingletonClass = SingletonClass()
onUpdate(bookUiState.bookDetails.copy(userId = SingletonClass.getUserId()))
Column(
Modifier
.fillMaxWidth()
.padding(all = 10.dp)
.verticalScroll(ScrollState(1))
) {
Spacer(modifier = Modifier.size(10.dp))
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = bookUiState.bookDetails.title,
onValueChange = { onUpdate(bookUiState.bookDetails.copy(title = it)) },
label = { Text("Название книги") },
singleLine = true
)
AuthorDropDown(
authorUiState = authorUiState,
authorsListUiState = authorsListUiState,
onAuthorUpdate = {
onUpdate(bookUiState.bookDetails.copy(authorId = it.uid))
onAuthorUpdate(it)
}
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = bookUiState.bookDetails.description,
onValueChange = { onUpdate(bookUiState.bookDetails.copy(description = it)) },
label = { Text("Описание") },
)
ImageUploader(bitmap = bookUiState.bookDetails.cover, onResult = { bitmap: Bitmap -> handleImageUpload(bitmap)} )
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = bookUiState.bookDetails.content,
onValueChange = { onUpdate(bookUiState.bookDetails.copy(content = it)) },
label = { Text("Содержание") },
)
Button(
onClick = onClick,
enabled = bookUiState.isEntryValid,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "Сохранить")
}
}
}

View File

@ -14,9 +14,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.example.myapplication.composeui.ViewModel.BookPageViewModel
import com.example.myapplication.db.database.AppDatabase import com.example.myapplication.db.database.AppDatabase
import com.example.myapplication.db.model.BookWithAuthor import com.example.myapplication.db.model.BookWithAuthor
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
@ -24,12 +27,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
fun BookRead(id: Int) { fun BookRead(id: Int, viewModel: BookPageViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val context = LocalContext.current val scope = rememberCoroutineScope()
val (bookWithAuthor, setBookWithAuthor) = remember { mutableStateOf<BookWithAuthor?>(null) } val book = viewModel.bookPageUiState
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
setBookWithAuthor(AppDatabase.getInstance(context).bookDao().getByUid(id)) viewModel.refreshState()
} }
} }
Column( Column(
@ -37,7 +40,7 @@ fun BookRead(id: Int) {
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp) .padding(all = 10.dp)
){ ){
Text(text = bookWithAuthor?.book?.content ?: "", fontSize=22.sp) Text(text = book?.book?.content ?: "", fontSize=22.sp)
} }
} }
@ -52,4 +55,4 @@ fun BookReadPreview() {
BookRead(id = 0) BookRead(id = 0)
} }
} }
} }

View File

@ -20,59 +20,36 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext 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.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.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.composeui.ViewModel.SearchPageViewModel
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.Book import com.example.myapplication.db.model.Book
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
fun BookSearch(navController: NavController?, text: String) { fun BookSearch(navController: NavController, searchStr: String, viewModel: SearchPageViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val context = LocalContext.current val coroutineScope = rememberCoroutineScope()
val books = remember { mutableStateListOf<Book>() } val searchPageUiState = viewModel.searchPageUiState
LaunchedEffect(Unit) { val searchBook: List<Book> = searchPageUiState.bookList
withContext(Dispatchers.IO) { LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 180.dp)) {
AppDatabase.getInstance(context).bookDao().getBySearch(text).collect { data -> items(searchBook.size) { index ->
books.clear() BookCell(navController = navController, book = searchBook[index]!!.copy())
books.addAll(data)
}
}
}
Column(Modifier.padding(all = 10.dp)) {
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 180.dp)) {
items (books){ book ->
key(book.uid) {
val bookId = Screen.BookView.route.replace("{id}", book.uid.toString())
Image(
painter = painterResource(id = book.cover!!),
contentDescription = book.title,
modifier = Modifier
.height(300.dp)
.padding(horizontal = 5.dp)
.clickable(
enabled = true,
onClick = { navController?.navigate(bookId) }
)
)
}
}
}
Spacer(Modifier.padding(bottom = 10.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
onClick = { navController?.navigate(Screen.Loader.route) }) {
Text(stringResource(id = R.string.add_book))
} }
} }
} }
@ -85,7 +62,7 @@ fun BookSearchPreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
BookSearch(navController = null, "") BookSearch(navController = rememberNavController(), "")
} }
} }
} }

View File

@ -1,76 +1,94 @@
package com.example.myapplication.composeui package com.example.myapplication.composeui
import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.compose.foundation.Image
import androidx.compose.material3.Button
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.material3.Surface
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize
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.material3.Button import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.OutlinedTextField
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.runtime.LaunchedEffect
import androidx.compose.runtime.key import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
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.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.withContext
import kotlinx.coroutines.Dispatchers
import com.example.myapplication.db.database.AppDatabase;
import com.example.myapplication.db.model.BookWithAuthor;
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.composeui.ViewModel.BookPageViewModel
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
private fun getBookImage(context: Context, imageId: String): Bitmap {
val inputStream = context.assets.open("${imageId}.jpg")
val bitmap = BitmapFactory.decodeStream(inputStream)
return bitmap
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookView(navController: NavController?, id: Int) { fun BookView(navController: NavController, id: Int, viewModel: BookPageViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val context = LocalContext.current val scope = rememberCoroutineScope()
val (bookWithAuthor, setBookWithAuthor) = remember { mutableStateOf<BookWithAuthor?>(null) } val book = viewModel.bookPageUiState
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
withContext(Dispatchers.IO) { viewModel.refreshState()
setBookWithAuthor(AppDatabase.getInstance(context).bookDao().getByUid(id))
}
} }
val context = LocalContext.current
Column( Column(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp) .padding(all = 10.dp)
.verticalScroll(rememberScrollState())
) { ) {
OutlinedTextField(modifier = Modifier.fillMaxWidth(), Image(
value = bookWithAuthor?.book?.title ?: "", onValueChange = {}, readOnly = true, bitmap = book.book?.cover?.asImageBitmap() ?: getBookImage(context, "img").asImageBitmap(),
label = { contentDescription = book.book?.title,
Text(stringResource(id = R.string.book_title)) contentScale = ContentScale.FillBounds,
}
)
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = bookWithAuthor?.authorName ?: "", onValueChange = {}, readOnly = true,
label = {
Text(stringResource(id = R.string.author_name))
}
)
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = bookWithAuthor?.book?.description ?: "", onValueChange = {}, readOnly = true,
label = {
Text(stringResource(id = R.string.description))
}
)
Spacer(Modifier.padding(bottom = 10.dp))
Button(
modifier = Modifier modifier = Modifier
.fillMaxSize()
.requiredSize(400.dp, 400.dp)
.padding(0.dp, 10.dp)
)
Column(
Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp), .padding(all = 10.dp)
onClick = { navController?.navigate(Screen.BookRead.route.replace("{id}", id.toString())) }) { ) {
Text(stringResource(id = R.string.read_book)) OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = book.book?.title ?: "", onValueChange = {}, readOnly = true,
label = {
Text(stringResource(id = R.string.book_title))
}
)
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = book.book?.description ?: "", onValueChange = {}, readOnly = true,
label = {
Text(stringResource(id = R.string.description))
}
)
Button(
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
onClick = { navController?.navigate(Screen.BookRead.route.replace("{id}", id.toString())) }) {
Text(stringResource(id = R.string.read_book))
}
} }
} }
} }
@ -83,7 +101,7 @@ fun BookViewPreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
BookView(navController = null, id = 0) BookView(navController = rememberNavController(), id = 0)
} }
} }
} }

View File

@ -2,89 +2,70 @@ package com.example.myapplication.composeui
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
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.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.height
import androidx.compose.ui.res.painterResource
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
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.runtime.key
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.compose.runtime.remember
import androidx.navigation.NavController import androidx.navigation.NavController
import kotlinx.coroutines.Dispatchers
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.withContext
import com.example.myapplication.db.database.AppDatabase;
import com.example.myapplication.db.model.Book;
import com.example.myapplication.R
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.myapplication.composeui.ViewModel.BookListViewModel
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.db.model.Book
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
@Composable @Composable
fun Catalog(navController: NavController?) { fun Catalog(navController: NavController, viewModel: BookListViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val context = LocalContext.current val coroutineScope = rememberCoroutineScope()
val books = remember { mutableStateListOf<Book>() } val bookListUiState = viewModel.bookListUiState
LaunchedEffect(Unit) { val pagingBook: LazyPagingItems<Book> = viewModel.bookPagedData.collectAsLazyPagingItems()
withContext(Dispatchers.IO) { Column() {
AppDatabase.getInstance(context).bookDao().getAll().collect { data ->
books.clear()
books.addAll(data)
}
}
}
Column(Modifier.padding(all = 10.dp)) {
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 180.dp)) {
items (books){ book ->
key(book.uid) {
val bookId = Screen.BookView.route.replace("{id}", book.uid.toString())
Image(
painter = painterResource(id = book.cover!!),
contentDescription = book.title,
modifier = Modifier
.height(300.dp)
.padding(horizontal = 5.dp)
.clickable(
enabled = true,
onClick = { navController?.navigate(bookId) }
)
)
}
}
}
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 10.dp), .padding(all = 10.dp),
onClick = { navController?.navigate(Screen.Loader.route) }) { onClick = {
Text(stringResource(id = R.string.add_book)) val route = Screen.BookEdit.route.replace("{id}", 0.toString())
navController.navigate(route)
}
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Добавить",
)
Text("Добавить")
}
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 180.dp)) {
items(pagingBook.itemCount) { index ->
BookCell(navController = navController, book = pagingBook[index]!!.copy())
}
} }
} }
} }
@Preview(name = "Light Mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable
fun CatalogPreview() { private fun CatalogPreview(){
MyApplicationTheme { MyApplicationTheme{
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Catalog(navController = null) Main(navController = rememberNavController())
} }
} }
} }

View File

@ -1,6 +1,7 @@
package com.example.myapplication.composeui package com.example.myapplication.composeui
import android.content.res.Configuration import android.content.res.Configuration
import android.widget.Toast
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
@ -23,7 +24,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.rememberNavController
import com.example.myapplication.MainActivity
import com.example.myapplication.SingletonClass
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.User import com.example.myapplication.db.model.User
@ -35,16 +40,16 @@ import kotlinx.coroutines.GlobalScope
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Enter(navController: NavController?) { fun Enter(navController: NavController) {
var SingletonClass = SingletonClass()
val scope = rememberCoroutineScope()
val context = LocalContext.current
var login by remember{mutableStateOf("")} var login by remember{mutableStateOf("")}
var password by remember{mutableStateOf("")} var password by remember{mutableStateOf("")}
val context = LocalContext.current Column(
LaunchedEffect(Unit) { Modifier
withContext(Dispatchers.IO) { .fillMaxWidth()
AppDatabase.getInstance(context).userDao().getAll() .padding(all = 40.dp)) {
}
}
Column(Modifier.fillMaxWidth().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 = login, onValueChange = { login = it },
@ -60,17 +65,15 @@ fun Enter(navController: NavController?) {
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp), .padding(all = 10.dp),
onClick = { onClick = {
var user: User? scope.launch {if( AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString()) != null){
GlobalScope.launch (Dispatchers.Main) { SingletonClass.setUserId(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.uid!!)
user = AppDatabase.getInstance(context).userDao().tryLogin(login, password) SingletonClass.setRole(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.role!!)
if (user != null) { navController?.navigate(Screen.Profile.route)
//AppDatabase.getInstance(context).userDao().setLogined(user!!.uid!!)
navController?.navigate(Screen.Profile.route)
} else {
password = ""
login = "Неверный логин или пароль"
}
} }
else{
val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT)
toast.show()
}}
}) { }) {
Text(stringResource(id = R.string.enter)) Text(stringResource(id = R.string.enter))
} }
@ -85,7 +88,7 @@ fun EnterPreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Enter(navController = null) Enter(navController = rememberNavController())
} }
} }
} }

View File

@ -2,68 +2,69 @@ package com.example.myapplication.composeui
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
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.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
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.runtime.rememberCoroutineScope
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
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.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.R 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.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.model.Author import com.example.myapplication.db.model.Author
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 ListAuthors() { fun ListAuthors(navController: NavController, viewModel: AuthorListViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val context = LocalContext.current val coroutineScope = rememberCoroutineScope()
val authors = remember { mutableStateListOf<Author>() } val authorListUiState = viewModel.authorListUiState
LaunchedEffect(Unit) { val pagingAuthor: LazyPagingItems<Author> = viewModel.authorPagedData.collectAsLazyPagingItems()
withContext(Dispatchers.IO) { Column() {
AppDatabase.getInstance(context).authorDao().getAll().collect { data ->
authors.clear()
authors.addAll(data)
}
}
}
Column(Modifier.padding(all = 10.dp)) {
authors.forEach{ author ->
Text(author.name, fontSize = 20.sp)
}
Spacer(Modifier.padding(bottom = 10.dp))
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp), .padding(all = 10.dp),
onClick = {}) { onClick = {
Text(stringResource(id = R.string.add_book)) val route = Screen.AuthorEdit.route.replace("{id}", 0.toString())
navController.navigate(route)
}
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Добавить",
)
Text("Добавить")
}
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 180.dp)) {
items(pagingAuthor.itemCount) { index ->
AuthorCell(navController = navController, author = pagingAuthor[index]!!.copy())
}
} }
} }
} }
@Preview(name = "Light Mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable
fun ListAuthorsPreview() { private fun ListAuthorsPreview(){
MyApplicationTheme { MyApplicationTheme{
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
ListAuthors() Main(navController = rememberNavController())
} }
} }
} }

View File

@ -1,67 +0,0 @@
package com.example.myapplication.composeui
import android.content.res.Configuration
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.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.myapplication.R
import androidx.compose.foundation.BorderStroke
import androidx.compose.material3.ButtonDefaults
import com.example.myapplication.ui.theme.MyApplicationTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Loader() {
Column(Modifier.fillMaxWidth().padding(all = 40.dp)) {
Text(stringResource(id = R.string.help_me_pls))
Spacer(Modifier.padding(bottom = 20.dp))
val titleStr = remember{ mutableStateOf("") }
Text(stringResource(id = R.string.book_title))
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = titleStr.value, onValueChange = { newText -> titleStr.value = newText },
)
Spacer(Modifier.padding(bottom = 10.dp))
val authorStr = remember{ mutableStateOf("") }
Text(stringResource(id = R.string.author_name))
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = authorStr.value, onValueChange = { newText -> authorStr.value = newText },
)
Spacer(Modifier.padding(bottom = 20.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
onClick = {}, colors = ButtonDefaults.buttonColors(containerColor = Color.LightGray, contentColor = Color.Black),
border = BorderStroke(3.dp, Color.DarkGray)) {
Text(stringResource(id = R.string.load_book))
}
}
}
@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 LoaderPreview() {
MyApplicationTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
Loader()
}
}
}

View File

@ -25,9 +25,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.myapplication.R import com.example.myapplication.R
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.navigation.compose.rememberNavController
@Composable @Composable
fun Main(navController: NavController?) { fun Main(navController: NavController) {
Column(Modifier.padding(all = 40.dp)) { Column(Modifier.padding(all = 40.dp)) {
Text(stringResource(id = R.string.welcome), modifier = Modifier.fillMaxWidth(), fontSize = 24.sp, textAlign = TextAlign.Center) Text(stringResource(id = R.string.welcome), modifier = Modifier.fillMaxWidth(), fontSize = 24.sp, textAlign = TextAlign.Center)
Spacer(Modifier.padding(bottom = 20.dp)) Spacer(Modifier.padding(bottom = 20.dp))
@ -64,7 +65,7 @@ fun MainPreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Main(navController = null) Main(navController = rememberNavController())
} }
} }
} }

View File

@ -1,7 +1,10 @@
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.BorderStroke
import androidx.compose.foundation.Image
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
@ -29,223 +32,55 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment 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.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
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.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation 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.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 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.User 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.composeui.ViewModel.UserPageViewModel
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Profile(navController: NavController?) { fun Profile(navController: NavController) {
val openDialogEdit = remember { mutableStateOf(false) } var SingletonClass = SingletonClass()
val openDialogExit = remember { mutableStateOf(false) } Column(Modifier.padding(all = 40.dp)) {
val openDialogDelete = remember { mutableStateOf(false) } Image(
val context = LocalContext.current painter = painterResource(id = R.drawable.user),
val user = remember { mutableStateOf<User>(User("", "", "", "USER")) } contentDescription = "logo",
val userOldPsswd = remember { mutableStateOf("") } contentScale = ContentScale.FillWidth,
val userNewPsswd = remember { mutableStateOf("") } modifier = Modifier.fillMaxWidth()
val userNewPsswdConf = remember { mutableStateOf("") } .clickable(
LaunchedEffect(Unit) { enabled = true,
withContext(Dispatchers.IO) {
user.value = AppDatabase.getInstance(context).userDao().getByUid(2)
}
}
Column(Modifier.padding(all = 10.dp), horizontalAlignment = Alignment.CenterHorizontally) {
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = user.value.login, onValueChange = {user.value.login = it},
label = {
Text(stringResource(id = R.string.login))
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = user.value.password, onValueChange = {user.value.password = it},
label = {
Text(stringResource(id = R.string.password))
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = user.value.email, onValueChange = {user.value.email = it},
label = {
Text(stringResource(id = R.string.email))
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
)
Spacer(modifier = Modifier.padding(all = 20.dp))
Button(
onClick = { openDialogEdit.value = true },
shape = CircleShape,
modifier = Modifier.fillMaxWidth(fraction = 0.75f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary
)
) {
// Inner content including an icon and a text label
Icon(
imageVector = Icons.Default.Create,
contentDescription = "Favorite",
modifier = Modifier.size(20.dp)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(text = "Редактировать")
}
Button(
onClick = { openDialogExit.value = true },
shape = CircleShape,
modifier = Modifier.fillMaxWidth(fraction = 0.75f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.error
)
) {
// Inner content including an icon and a text label
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Favorite",
modifier = Modifier.size(20.dp)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(text = "Выход")
}
OutlinedButton(
onClick = { openDialogDelete.value = true },
shape = CircleShape,
modifier = Modifier.fillMaxWidth(fraction = 0.75f),
border= BorderStroke(1.dp, MaterialTheme.colorScheme.error),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.error
)
) {
// Inner content including an icon and a text label
Icon(
imageVector = Icons.Outlined.Clear,
contentDescription = "Удалить",
modifier = Modifier.size(20.dp)
)
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
Text(text = "Удалить аккаунт")
}
}
if (openDialogEdit.value) {
AlertDialog(
icon = {
Icon(Icons.Default.Edit, contentDescription = "Example Icon")
},
title = {
Text(text = "Подтверждение")
},
text = {
Text(text = "Вы хотите применить изменения?")
},
onDismissRequest = {
openDialogEdit.value = false
},
confirmButton = {
TextButton(
onClick = { onClick = {
openDialogEdit.value = false navController?.navigate(
/* TODO */ Screen.ProfileEdit.route.replace(
"{id}",
SingletonClass.getUserId().toString()
)
)
} }
) { )
Text("Да")
}
},
dismissButton = {
TextButton(
onClick = {
openDialogEdit.value = false
}
) {
Text("Нет")
}
}
)
}
if (openDialogExit.value) {
AlertDialog(
icon = {
Icon(Icons.Default.Close, contentDescription = "Example Icon")
},
title = {
Text(text = "Подтверждение")
},
text = {
Text(text = "Вы хотите выйти из аккаунта?")
},
onDismissRequest = {
openDialogExit.value = false
},
confirmButton = {
TextButton(
onClick = {
openDialogExit.value = false
navController?.navigate(Screen.Enter.route)
}
) {
Text("Да")
}
},
dismissButton = {
TextButton(
onClick = {
openDialogExit.value = false
}
) {
Text("Нет")
}
}
)
}
if (openDialogDelete.value) {
AlertDialog(
icon = {
Icon(Icons.Default.Close, contentDescription = "Example Icon")
},
title = {
Text(text = "Подтверждение")
},
text = {
Text(text = "Вы хотите удалить аккаунт?")
},
onDismissRequest = {
openDialogDelete.value = false
},
confirmButton = {
TextButton(
onClick = {
openDialogDelete.value = false
/* TODO */
}
) {
Text("Да")
}
},
dismissButton = {
TextButton(
onClick = {
openDialogDelete.value = false
}
) {
Text("Нет")
}
}
) )
Spacer (Modifier.padding(bottom = 10.dp))
Text("Открыть профиль", modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
} }
} }
@ -258,7 +93,7 @@ fun ProfilePreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Profile(navController = null) Profile(navController = rememberNavController())
} }
} }
} }

View File

@ -0,0 +1,115 @@
package com.example.myapplication.composeui
import android.annotation.SuppressLint
import android.content.res.Configuration
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.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.example.myapplication.R
import com.example.myapplication.composeui.ViewModel.UserDetails
import com.example.myapplication.composeui.ViewModel.UserEditViewModel
import com.example.myapplication.composeui.ViewModel.UserPageViewModel
import com.example.myapplication.composeui.ViewModel.UserUiState
import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.ui.theme.MyApplicationTheme
import kotlinx.coroutines.launch
@Composable
fun ProfileEdit(navController: NavController, viewModel: UserEditViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val scope = rememberCoroutineScope()
viewModel.userUiState.userDetails.login.isBlank()
ProfileEdit(
navController = navController,
userUiState = viewModel.userUiState,
onClick = {
scope.launch {
viewModel.saveUser()
navController.popBackStack()
}
},
onUpdate = viewModel::updateUiState
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ProfileEdit(
navController: NavController,
userUiState: UserUiState,
onClick: () -> Unit,
onUpdate: (UserDetails) -> Unit
){
LazyColumn() {
item {
OutlinedTextField(
value = userUiState.userDetails.email,
onValueChange = { onUpdate(userUiState.userDetails.copy(email = it)) },
singleLine = true,
label = { Text("Почта", fontSize = 20.sp) },
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 5.dp)
)
OutlinedTextField(
value = userUiState.userDetails.login,
onValueChange = { onUpdate(userUiState.userDetails.copy(login = it)) },
label = { Text("Логин", fontSize = 20.sp) },
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 5.dp)
)
OutlinedTextField(
value = userUiState.userDetails.password,
onValueChange = { onUpdate(userUiState.userDetails.copy(password = it)) },
singleLine = true,
label = { Text("Пароль", fontSize = 20.sp) },
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 5.dp)
)
Button(
onClick = onClick,
enabled = userUiState.isEntryValid,
modifier = Modifier.fillMaxWidth()
.padding(10.dp, 0.dp),
) {
Text("Изменить")
}
Button(
onClick = { navController.popBackStack() },
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 0.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color.LightGray, contentColor = Color.Black))
{
Text("Назад")
}
}
}
}

View File

@ -18,38 +18,53 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
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.db.model.User
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Registration(navController: NavController?) { fun Registration(navController: NavController) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
Column(Modifier.fillMaxWidth().padding(all = 40.dp)) { Column(Modifier.fillMaxWidth().padding(all = 40.dp)) {
val mailStr = remember{mutableStateOf("")} 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 = mailStr.value, onValueChange = { newText -> mailStr.value = newText }, value = mail, onValueChange = { mail = it },
) )
Spacer(Modifier.padding(bottom = 10.dp)) Spacer(Modifier.padding(bottom = 10.dp))
val loginStr = remember{mutableStateOf("")} 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 = loginStr.value, onValueChange = { newText -> loginStr.value = newText }, value = login, onValueChange = { login = it },
) )
Spacer(Modifier.padding(bottom = 10.dp)) Spacer(Modifier.padding(bottom = 10.dp))
val passwordStr = remember{mutableStateOf("")} var password by remember{mutableStateOf("")}
Text(stringResource(id = R.string.password)) Text(stringResource(id = R.string.password))
OutlinedTextField(modifier = Modifier.fillMaxWidth(), OutlinedTextField(modifier = Modifier.fillMaxWidth(),
value = passwordStr.value, onValueChange = { newText -> passwordStr.value = newText }, value = password, onValueChange = { password = it },
) )
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 = { navController?.navigate(Screen.Enter.route) }) { 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))
} }
} }
@ -63,7 +78,7 @@ fun RegistrationPreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Registration(navController = null) Registration(navController = rememberNavController())
} }
} }
} }

View File

@ -27,6 +27,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.ui.theme.MyApplicationTheme import com.example.myapplication.ui.theme.MyApplicationTheme
@ -39,7 +40,10 @@ fun Search(navController: NavController?) {
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(all = 40.dp)) { .padding(all = 40.dp)) {
Column(Modifier.background(color = colorResource(R.color.green_200)).padding(all = 20.dp)) { Column(
Modifier
.background(color = colorResource(R.color.green_200))
.padding(all = 20.dp)) {
Text(stringResource(id = R.string.welcome), color = Color.White) Text(stringResource(id = R.string.welcome), color = Color.White)
Text(stringResource(id = R.string.book_world), color = Color.White) Text(stringResource(id = R.string.book_world), color = Color.White)
} }
@ -57,7 +61,7 @@ fun Search(navController: NavController?) {
if(!searchStr.isEmpty()){ if(!searchStr.isEmpty()){
navController?.navigate( navController?.navigate(
Screen.BookSearch.route.replace( Screen.BookSearch.route.replace(
"{text}", "{searchStr}",
searchStr searchStr
) )
) )
@ -76,7 +80,7 @@ fun SearchPreview() {
Surface( Surface(
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
Search(navController=null) Search(navController = rememberNavController())
} }
} }
} }

View File

@ -0,0 +1,46 @@
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 com.example.myapplication.db.model.Author
import kotlinx.coroutines.launch
import com.example.myapplication.db.respository.AuthorRepository
class AuthorDropDownViewModel(
private val authorRepository: AuthorRepository
) : ViewModel() {
var authorsListUiState by mutableStateOf(AuthorsListUiState())
private set
var authorsUiState by mutableStateOf(AuthorsUiState())
private set
init {
viewModelScope.launch {
authorsListUiState = AuthorsListUiState(authorRepository.getAllDrop())
}
}
fun setCurrentAuthor(authorId: Int) {
val author: Author? =
authorsListUiState.authorList.firstOrNull { author -> author.uid == authorId }
author?.let { updateUiState(it) }
}
fun updateUiState(author: Author) {
authorsUiState = AuthorsUiState(
author = author
)
}
}
data class AuthorsListUiState(val authorList: List<Author> = listOf())
data class AuthorsUiState(
val author: Author? = null
)
fun Author.toUiState() = AuthorsUiState(author = Author(uid = uid, name = name))

View File

@ -0,0 +1,71 @@
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 com.example.myapplication.db.model.Author
import com.example.myapplication.db.respository.AuthorRepository
import kotlinx.coroutines.launch
class AuthorEditViewModel(savedStateHandle: SavedStateHandle,
private val authorRepository: AuthorRepository
) : ViewModel(){
var authorUiState by mutableStateOf(AuthorUiState())
private set
private val authorUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
if (authorUid > 0) {
authorUiState = authorRepository.getByUid(authorUid)
.toUiState(true)
}
}
}
fun updateUiState(authorDetails: AuthorDetails) {
authorUiState = AuthorUiState(
authorDetails = authorDetails,
isEntryValid = validateInput(authorDetails)
)
}
suspend fun saveAuthor() {
if (validateInput()) {
if (authorUid > 0) {
authorRepository.update(authorUiState.authorDetails.toAuthor(authorUid))
} else {
authorRepository.insert(authorUiState.authorDetails.toAuthor())
}
}
}
private fun validateInput(uiState: AuthorDetails = authorUiState.authorDetails): Boolean {
return with(uiState) {
name.isNotBlank()
}
}
}
data class AuthorUiState(
val authorDetails: AuthorDetails = AuthorDetails(),
val isEntryValid: Boolean = false
)
data class AuthorDetails(
val name: String = ""
)
fun AuthorDetails.toAuthor(uid: Int = 0): Author = Author(
uid = uid,
name = name
)
fun Author.toDetails(): AuthorDetails = AuthorDetails(
name = name
)
fun Author.toUiState(isEntryValid: Boolean = false): AuthorUiState = AuthorUiState(
authorDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,32 @@
package com.example.myapplication.composeui.ViewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.example.myapplication.db.model.Author
import com.example.myapplication.db.respository.AuthorRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class AuthorListViewModel(
private val authorRepository: AuthorRepository
) : ViewModel(){
var authorListUiState by mutableStateOf(AuthorListUiState())
private set
init {
viewModelScope.launch {
refreshState()
}
}
suspend fun refreshState() {
authorListUiState = AuthorListUiState(authorRepository.getAll())
}
val authorPagedData: Flow<PagingData<Author>> = authorRepository.loadAllAuthorsPaged()
suspend fun deleteAuthor(author: Author) {
authorRepository.delete(author)
}
}
data class AuthorListUiState(val authorList: List<Author> = listOf())

View File

@ -0,0 +1,91 @@
package com.example.myapplication.composeui.ViewModel
import android.graphics.Bitmap
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.example.myapplication.db.respository.BookRepository
import androidx.lifecycle.viewModelScope
import com.example.myapplication.db.model.Book
import kotlinx.coroutines.launch
class BookEditViewModel(savedStateHandle: SavedStateHandle,
private val bookRepository: BookRepository) : ViewModel(){
var bookUiState by mutableStateOf(BookUiState())
private set
private val bookUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
if (bookUid > 0) {
bookUiState = bookRepository.getByUid(bookUid)
.toUiState(true)
}
}
}
fun updateUiState(bookDetails: BookDetails) {
bookUiState = BookUiState(
bookDetails = bookDetails,
isEntryValid = validateInput(bookDetails)
)
}
suspend fun saveBook() {
if (validateInput()) {
if (bookUid > 0) {
bookRepository.update(bookUiState.bookDetails.toBook(bookUid))
} else {
bookRepository.insert(bookUiState.bookDetails.toBook())
}
}
}
private fun validateInput(uiState: BookDetails = bookUiState.bookDetails): Boolean {
return with(uiState) {
title.isNotBlank()
&& description.isNotBlank()
&& content.isNotBlank()
&& cover != null
&& authorId >= 0
&& userId >= 0
}
}
}
data class BookUiState(
val bookDetails: BookDetails = BookDetails(),
val isEntryValid: Boolean = false
)
data class BookDetails(
val title: String = "",
val description: String = "",
val content: String = "",
val cover: Bitmap? = null,
val authorId: Int = 0,
val userId: Int = 0,
)
fun BookDetails.toBook(uid: Int = 0): Book = Book(
uid = uid,
title = title,
description = description,
content = content,
cover = cover,
authorId = authorId,
userId = userId
)
fun Book.toDetails(): BookDetails = BookDetails(
title = title,
description = description,
content = content,
cover = cover,
authorId = authorId,
userId = userId
)
fun Book.toUiState(isEntryValid: Boolean = false): BookUiState = BookUiState(
bookDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,33 @@
package com.example.myapplication.composeui.ViewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.example.myapplication.db.model.Book
import com.example.myapplication.db.respository.BookRepository
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.Flow
class BookListViewModel(
private val bookRepository: BookRepository
) : ViewModel() {
var bookListUiState by mutableStateOf(BookListUiState())
private set
init {
viewModelScope.launch {
refreshState()
}
}
suspend fun refreshState() {
bookListUiState = BookListUiState(bookRepository.getAll())
}
val bookPagedData: Flow<PagingData<Book>> = bookRepository.loadAllBooksPaged()
suspend fun deleteBook(book: Book) {
bookRepository.delete(book)
}
}
data class BookListUiState(val bookList: List<Book> = listOf())

View File

@ -0,0 +1,33 @@
package com.example.myapplication.composeui.ViewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.SavedStateHandle
import com.example.myapplication.db.model.Book
import com.example.myapplication.db.model.BookWithAuthor
import com.example.myapplication.db.respository.BookRepository
import kotlinx.coroutines.launch
class BookPageViewModel(private val bookRespository: BookRepository, savedStateHandle: SavedStateHandle) : ViewModel(){
private val bookId: Int = checkNotNull(savedStateHandle["id"])
var bookPageUiState by mutableStateOf(BookPageUiState())
private set
init {
viewModelScope.launch {
if (bookId > 0) {
refreshState()
}
}
}
suspend fun refreshState() {
bookPageUiState = BookPageUiState(bookRespository.getByUid(bookId))
}
suspend fun deleteBook(book: Book) {
bookRespository.delete(book)
}
}
data class BookPageUiState(val book: Book? = null)

View File

@ -0,0 +1,126 @@
package com.example.myapplication.composeui.ViewModel
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.myapplication.R
@Composable
fun ImageUploader(
bitmap: Bitmap?,
onResult: (Bitmap) -> Unit
) {
val context = LocalContext.current
val title: String = if (bitmap == null) {
"Не загружено"
} else {
"Размер: " + bitmap.width + " " + bitmap.height
}
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent(),
onResult = { uri: Uri? ->
uri?.let {
val inputStream = context.contentResolver.openInputStream(uri)
val newBitmap: Bitmap = BitmapFactory.decodeStream(inputStream)
val scaledBitmap = resizeBitmapWithAspectRatio(newBitmap, 200)
onResult(scaledBitmap)
}
}
)
Row(
modifier = Modifier.height(IntrinsicSize.Min)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(10.dp)
)
.weight(0.25F)
.aspectRatio(1F)
) {
if (bitmap != null) {
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = null,
modifier = Modifier.width(100.dp)
)
} else {
Icon(
painter = painterResource(R.drawable.ic_camera),
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground
)
}
}
Spacer(modifier = Modifier.width(10.dp))
Column(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(10.dp)
)
.fillMaxHeight()
.padding(10.dp)
.weight(0.75F)
) {
Text(
text = title,
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.labelMedium
)
Spacer(modifier = Modifier.weight(1F))
Button(
onClick = { launcher.launch("image/*") },
){
Text(text = "+")
}
}
}
}
fun resizeBitmapWithAspectRatio(bitmap: Bitmap, maxHeight: Int): Bitmap {
if (bitmap.height <= maxHeight) {
return bitmap
}
val aspectRatio = bitmap.width.toFloat() / bitmap.height
val newWidth = (maxHeight * aspectRatio).toInt()
return Bitmap.createScaledBitmap(bitmap, newWidth, maxHeight, true)
}
@Preview(name = "Light Mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun CuteImageLoaderPreview() {
}

View File

@ -0,0 +1,31 @@
package com.example.myapplication.composeui.ViewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.example.myapplication.db.model.Book
import com.example.myapplication.db.respository.BookRepository
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.Flow
class SearchPageViewModel(private val bookRepository: BookRepository, savedStateHandle: SavedStateHandle) : ViewModel(){
private val searchStr: String = checkNotNull(savedStateHandle["searchStr"])
var searchPageUiState by mutableStateOf(SearchPageUiState())
private set
init {
viewModelScope.launch {
refreshState()
}
}
suspend fun refreshState() {
searchPageUiState = SearchPageUiState(bookRepository.getBySearch(searchStr))
}
val bookPagedData: Flow<PagingData<Book>> = bookRepository.loadAllBooksPaged()
}
data class SearchPageUiState(val bookList: List<Book> = listOf())

View File

@ -0,0 +1,84 @@
package com.example.myapplication.composeui.ViewModel
import android.graphics.Bitmap
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.example.myapplication.db.respository.UserRepository
import androidx.lifecycle.viewModelScope
import com.example.myapplication.db.model.User
import kotlinx.coroutines.launch
class UserEditViewModel(savedStateHandle: SavedStateHandle,
private val userRepository: UserRepository
) : ViewModel(){
var userUiState by mutableStateOf(UserUiState())
private set
private val userUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
if (userUid > 0) {
userUiState = userRepository.getByUid(userUid)
.toUiState(true)
}
}
}
fun updateUiState(userDetails: UserDetails) {
userUiState = UserUiState(
userDetails = userDetails,
isEntryValid = validateInput(userDetails)
)
}
suspend fun saveUser() {
if (validateInput()) {
if (userUid > 0) {
userRepository.update(userUiState.userDetails.toUser(userUid))
} else {
userRepository.insert(userUiState.userDetails.toUser())
}
}
}
private fun validateInput(uiState: UserDetails = userUiState.userDetails): Boolean {
return with(uiState) {
login.isNotBlank()
&& password.isNotBlank()
&& email.isNotBlank()
&& role.isNotBlank()
}
}
}
data class UserUiState(
val userDetails: UserDetails = UserDetails(),
val isEntryValid: Boolean = false
)
data class UserDetails(
val login: String = "",
val password: String = "",
val email: String = "",
val role: String = ""
)
fun UserDetails.toUser(uid: Int = 0): User = User(
uid = uid,
login = login,
password = password,
email = email,
role = role
)
fun User.toDetails(): UserDetails = UserDetails(
login = login,
password = password,
email = email,
role = role
)
fun User.toUiState(isEntryValid: Boolean = false): UserUiState = UserUiState(
userDetails = this.toDetails(),
isEntryValid = isEntryValid
)

View File

@ -0,0 +1,31 @@
package com.example.myapplication.composeui.ViewModel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.SavedStateHandle
import com.example.myapplication.db.model.User
import kotlinx.coroutines.launch
import com.example.myapplication.db.respository.UserRepository
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) {
refreshState()
}
}
}
suspend fun refreshState() {
userPageUiState = UserPageUiState(userRepository.getByUid(userId))
}
}
data class UserPageUiState(val user: User? = null)

View File

@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -19,8 +20,10 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
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
import androidx.compose.ui.unit.dp
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraph.Companion.findStartDestination
@ -37,13 +40,15 @@ 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
import com.example.myapplication.composeui.Profile import com.example.myapplication.composeui.Profile
import com.example.myapplication.composeui.ProfileEdit
import com.example.myapplication.composeui.Search import com.example.myapplication.composeui.Search
import com.example.myapplication.composeui.Loader
import com.example.myapplication.composeui.Catalog import com.example.myapplication.composeui.Catalog
import com.example.myapplication.composeui.BookView import com.example.myapplication.composeui.BookView
import com.example.myapplication.composeui.BookRead import com.example.myapplication.composeui.BookRead
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
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -51,29 +56,31 @@ fun Topbar(
navController: NavHostController, navController: NavHostController,
currentScreen: Screen? currentScreen: Screen?
) { ) {
TopAppBar( Box(modifier = Modifier.shadow(1.dp)) {
colors = TopAppBarDefaults.smallTopAppBarColors( TopAppBar(
containerColor = MaterialTheme.colorScheme.primary, colors = TopAppBarDefaults.smallTopAppBarColors(
titleContentColor = MaterialTheme.colorScheme.onPrimary, containerColor = MaterialTheme.colorScheme.primary,
), titleContentColor = MaterialTheme.colorScheme.onPrimary,
title = { ),
Text(stringResource(currentScreen?.resourceId ?: R.string.app_name)) title = {
}, Text(stringResource(currentScreen?.resourceId ?: R.string.app_name))
navigationIcon = { },
if ( navigationIcon = {
navController.previousBackStackEntry != null if (
&& (currentScreen == null || !currentScreen.showInBottomBar) navController.previousBackStackEntry != null
) { && (currentScreen == null || !currentScreen.showInBottomBar)
IconButton(onClick = { navController.navigateUp() }) { ) {
Icon( IconButton(onClick = { navController.navigateUp() }) {
imageVector = Icons.Filled.ArrowBack, Icon(
contentDescription = null, imageVector = Icons.Filled.ArrowBack,
tint = MaterialTheme.colorScheme.onPrimary contentDescription = null,
) tint = MaterialTheme.colorScheme.onPrimary
)
}
} }
} }
} )
) }
} }
@Composable @Composable
@ -117,10 +124,14 @@ fun Navhost(
composable(Screen.Registration.route) { Registration(navController) } composable(Screen.Registration.route) { Registration(navController) }
composable(Screen.Enter.route) { Enter(navController) } composable(Screen.Enter.route) { Enter(navController) }
composable(Screen.Profile.route) { Profile(navController) } composable(Screen.Profile.route) { Profile(navController) }
composable(
Screen.ProfileEdit.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let { ProfileEdit(navController) }
}
composable(Screen.Search.route) { Search(navController) } composable(Screen.Search.route) { Search(navController) }
composable(Screen.Loader.route) { Loader() }
composable(Screen.Catalog.route) { Catalog(navController) } composable(Screen.Catalog.route) { Catalog(navController) }
composable(Screen.ListAuthors.route) { ListAuthors() }
composable( composable(
Screen.BookView.route, Screen.BookView.route,
arguments = listOf(navArgument("id") { type = NavType.IntType }) arguments = listOf(navArgument("id") { type = NavType.IntType })
@ -134,10 +145,23 @@ fun Navhost(
backStackEntry.arguments?.let { BookRead(it.getInt("id")) } backStackEntry.arguments?.let { BookRead(it.getInt("id")) }
} }
composable( composable(
Screen.BookSearch.route, Screen.BookEdit.route,
arguments = listOf(navArgument("text") { type = NavType.StringType }) arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry -> ) { backStackEntry ->
backStackEntry.arguments?.let { BookSearch(navController = navController ,it.getString ("text", "")) } backStackEntry.arguments?.let { BookEdit(navController) }
}
composable(Screen.ListAuthors.route) { ListAuthors(navController) }
composable(
Screen.AuthorEdit.route,
arguments = listOf(navArgument("id") { type = NavType.IntType })
) { backStackEntry ->
backStackEntry.arguments?.let { AuthorEdit(navController) }
}
composable(
Screen.BookSearch.route,
arguments = listOf(navArgument("searchStr") { type = NavType.StringType })
) { backStackEntry ->
backStackEntry.arguments?.let { BookSearch(navController = navController ,it.getString ("searchStr", "")) }
} }
} }
} }

View File

@ -28,6 +28,9 @@ enum class Screen(
Profile( Profile(
"profile", R.string.profile, Icons.Filled.AccountCircle "profile", R.string.profile, Icons.Filled.AccountCircle
), ),
ProfileEdit(
"profile-edit/{id}", R.string.profile, showInBottomBar = false
),
Search( Search(
"search", R.string.search, Icons.Filled.Search "search", R.string.search, Icons.Filled.Search
), ),
@ -37,15 +40,18 @@ enum class Screen(
ListAuthors( ListAuthors(
"listauthors", R.string.listauthors, Icons.Filled.Face "listauthors", R.string.listauthors, Icons.Filled.Face
), ),
Loader( AuthorEdit(
"loader", R.string.loader, showInBottomBar = false "author-edit/{id}", R.string.book_view_title, showInBottomBar = false
), ),
BookSearch( BookSearch(
"book-search/{text}", R.string.search, showInBottomBar = false "book-search/{searchStr}", R.string.search, showInBottomBar = false
), ),
BookView( BookView(
"book-view/{id}", R.string.book_view_title, showInBottomBar = false "book-view/{id}", R.string.book_view_title, showInBottomBar = false
), ),
BookEdit(
"book-edit/{id}", R.string.book_view_title, showInBottomBar = false
),
BookRead( BookRead(
"book-read/{id}", R.string.book_view_title, showInBottomBar = false "book-read/{id}", R.string.book_view_title, showInBottomBar = false
); );

View File

@ -1,18 +1,24 @@
package com.example.myapplication.db.dao package com.example.myapplication.db.dao
import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.Query import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import com.example.myapplication.db.model.Author; import com.example.myapplication.db.model.Author
import com.example.myapplication.db.model.Book
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface AuthorDao { interface AuthorDao {
@Query("select * from authors order by author_name collate nocase asc") @Query("select * from authors order by author_name collate nocase asc")
fun getAll(): Flow<List<Author>> suspend fun getAll(): List<Author>
@Query("select * from authors order by author_name collate nocase asc")
suspend fun getAllDrop(): List<Author>
@Query("select * from authors where authors.uid = :uid")
suspend fun getByUid(uid: Int): Author
@Query("select * from authors order by author_name collate nocase asc")
fun loadAllAuthorsPaged(): PagingSource<Int, Author>
@Insert @Insert
suspend fun insert(author: Author) suspend fun insert(author: Author)

View File

@ -1,5 +1,6 @@
package com.example.myapplication.db.dao package com.example.myapplication.db.dao
import androidx.paging.PagingSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
@ -12,13 +13,13 @@ import com.example.myapplication.db.model.BookWithAuthor;
@Dao @Dao
interface BookDao { interface BookDao {
@Query("select * from books order by title collate nocase asc") @Query("select * from books order by title collate nocase asc")
fun getAll(): Flow<List<Book>> suspend fun getAll(): List<Book>
@Query("select * from books left join authors on books.author_id = authors.uid where books.uid = :uid") @Query("select * from books where books.uid = :uid")
suspend fun getByUid(uid: Int): BookWithAuthor suspend fun getByUid(uid: Int): Book
@Query("select * from books where books.title LIKE '%' || :searchStr || '%'") @Query("select * from books where books.title LIKE '%' || :searchStr || '%'")
fun getBySearch(searchStr: String): Flow<List<Book>> suspend fun getBySearch(searchStr: String): List<Book>
@Query("select * from books where books.user_id = :userId") @Query("select * from books where books.user_id = :userId")
fun getByUserId(userId: Int): Flow<List<Book>> fun getByUserId(userId: Int): Flow<List<Book>>
@ -26,6 +27,9 @@ interface BookDao {
@Query("select * from books where books.author_id = :authorId") @Query("select * from books where books.author_id = :authorId")
fun getByAuthorId(authorId: Int): Flow<List<Book>> fun getByAuthorId(authorId: Int): Flow<List<Book>>
@Query("select * from books order by title collate nocase asc")
fun loadAllBooksPaged(): PagingSource<Int, Book>
@Insert @Insert
suspend fun insert(book: Book) suspend fun insert(book: Book)

View File

@ -7,18 +7,22 @@ import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import com.example.myapplication.db.model.User; import com.example.myapplication.db.model.User;
import com.example.myapplication.db.model.Book;
@Dao @Dao
interface UserDao { interface UserDao {
@Query("select * from users") @Query("select * from users")
fun getAll(): Flow<List<User>> fun getAll(): Flow<List<User>>
@Query("select * from users where login = :login and password = :password")
suspend fun tryLogin(login: String, password: String): User?
@Query("select * from users where users.uid = :uid") @Query("select * from users where users.uid = :uid")
suspend fun getByUid(uid: Int): User suspend fun getByUid(uid: Int): User
@Query("select * from books where books.user_id = :userid")
fun getUserBooks(userid: Int): Flow<List<Book>>
@Query("select * from users where login = :login and password = :password")
suspend fun tryLogin(login: String, password: String): User?
@Insert @Insert
suspend fun insert(user: User) suspend fun insert(user: User)

View File

@ -0,0 +1,38 @@
package com.example.myapplication.db.database
import android.content.Context
import com.example.myapplication.db.dao.BookDao
import com.example.myapplication.db.dao.AuthorDao
import com.example.myapplication.db.dao.UserDao
import com.example.myapplication.db.respository.BookRepository
import com.example.myapplication.db.respository.AuthorRepository
import com.example.myapplication.db.respository.UserRepository
import com.example.myapplication.db.respository.OfflineBookRepository
import com.example.myapplication.db.respository.OfflineAuthorRepository
import com.example.myapplication.db.respository.OfflineUserRepository
interface AppContainer {
val bookRepository: BookRepository
val authorRepository: AuthorRepository
val userRepository: UserRepository
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 10
}
}
class AppDataContainer(private val context: Context) : AppContainer {
override val bookRepository: BookRepository by lazy {
OfflineBookRepository(AppDatabase.getInstance(context).bookDao())
}
override val authorRepository: AuthorRepository by lazy {
OfflineAuthorRepository(AppDatabase.getInstance(context).authorDao())
}
override val userRepository: UserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
companion object {
const val TIMEOUT = 5000L
}
}

View File

@ -1,9 +1,12 @@
package com.example.myapplication.db.database package com.example.myapplication.db.database
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.Database import androidx.room.Database
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -17,18 +20,19 @@ import com.example.myapplication.db.model.Author;
import com.example.myapplication.R import com.example.myapplication.R
@Database(entities = [Book::class, Author::class, User::class], version = 1, exportSchema = false) @Database(entities = [Book::class, Author::class, User::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun bookDao(): BookDao abstract fun bookDao(): BookDao
abstract fun authorDao(): AuthorDao abstract fun authorDao(): AuthorDao
abstract fun userDao(): UserDao abstract fun userDao(): UserDao
companion object { companion object {
private const val DB_NAME: String = "PMU" private const val DB_NAME: String = "PMU1"
@Volatile @Volatile
private var INSTANCE: AppDatabase? = null private var INSTANCE: AppDatabase? = null
private suspend fun populateDatabase() { private suspend fun populateDatabase(context: Context) {
INSTANCE?.let { database -> INSTANCE?.let { database ->
//Users //Users
val userDao = database.userDao() val userDao = database.userDao()
@ -46,10 +50,10 @@ abstract class AppDatabase : RoomDatabase() {
authorDao.insert(author3) authorDao.insert(author3)
// Books // Books
val bookDao = database.bookDao() val bookDao = database.bookDao()
val book1 = Book(1,"1984", "Роман о том, как репрессивная машина тоталитарного государства может уничтожить любую личность.", "Был холодный ясный апрельский день, и часы пробили тринадцать.\n" + "Уинстон Смит, прижав подбородок к груди и ежась от омерзительного ветра, быстро скользнул в стеклянные двери Дома Победы, но все же вихрь песка и пыли успел ворваться вместе с ним.", R.drawable.or1984, 1, 1) val book1 = Book(1,"1984", "Роман о том, как репрессивная машина тоталитарного государства может уничтожить любую личность.", "Был холодный ясный апрельский день, и часы пробили тринадцать.\n" + "Уинстон Смит, прижав подбородок к груди и ежась от омерзительного ветра, быстро скользнул в стеклянные двери Дома Победы, но все же вихрь песка и пыли успел ворваться вместе с ним.", getBookImage(context,"or1984"), 1, 1)
val book2 = Book(2,"Собачье сердце", "Роман о бродячем псе Шарике, превращенном в человека.", "Уу-у-у-у-гу-гуг-гуу! О, гляньте на меня, я погибаю.\n" + "Вьюга в подворотне ревёт мне отходную, и я вою с ней.\n" + "Пропал я, пропал.", R.drawable.dogsheart, 2, 1) val book2 = Book(2,"Собачье сердце", "Роман о бродячем псе Шарике, превращенном в человека.", "Уу-у-у-у-гу-гуг-гуу! О, гляньте на меня, я погибаю.\n" + "Вьюга в подворотне ревёт мне отходную, и я вою с ней.\n" + "Пропал я, пропал.", getBookImage(context,"dogsheart"), 2, 1)
val book3 = Book(3,"Вельд", "Рассказ о зависимости от технических устройств, потере человечности.", "— Джорджи, пожалуйста, посмотри детскую комнату.\n" + "А что с ней?\n" + "Не знаю.\n" + "— Так в чем же дело?", R.drawable.veld, 3, 1) val book3 = Book(3,"Вельд", "Рассказ о зависимости от технических устройств, потере человечности.", "— Джорджи, пожалуйста, посмотри детскую комнату.\n" + "А что с ней?\n" + "Не знаю.\n" + "— Так в чем же дело?", getBookImage(context,"veld"), 3, 1)
val book4 = Book(4,"Роковые яйца", "Рассказ, критикующий стремление к прогрессу без учета последствий.","16 апреля 1928 года, вечером, профессор зоологии IV государственного университета и директор зооинститута в Москве Персиков вошел в свой кабинет, помещающийся в зооинституте, что на улице Герцена.\n" + "Профессор зажег верхний матовый шар и огляделся.", R.drawable.eggs, 2, 2) val book4 = Book(4,"Роковые яйца", "Рассказ, критикующий стремление к прогрессу без учета последствий.","16 апреля 1928 года, вечером, профессор зоологии IV государственного университета и директор зооинститута в Москве Персиков вошел в свой кабинет, помещающийся в зооинституте, что на улице Герцена.\n" + "Профессор зажег верхний матовый шар и огляделся.", getBookImage(context,"eggs"), 2, 2)
bookDao.insert(book1) bookDao.insert(book1)
bookDao.insert(book2) bookDao.insert(book2)
bookDao.insert(book3) bookDao.insert(book3)
@ -68,7 +72,7 @@ abstract class AppDatabase : RoomDatabase() {
override fun onCreate(db: SupportSQLiteDatabase) { override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db) super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
populateDatabase() populateDatabase(appContext)
} }
} }
}) })
@ -76,5 +80,10 @@ abstract class AppDatabase : RoomDatabase() {
.also { INSTANCE = it } .also { INSTANCE = it }
} }
} }
private fun getBookImage(context: Context, imageId: String): Bitmap {
val inputStream = context.assets.open("${imageId}.jpg")
val bitmap = BitmapFactory.decodeStream(inputStream)
return bitmap
}
} }
} }

View File

@ -0,0 +1,20 @@
package com.example.myapplication.db.database
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.TypeConverter
import java.io.ByteArrayOutputStream
class Converters {
@TypeConverter
fun fromBitmap(bitmap: Bitmap): ByteArray {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 1, outputStream)
return outputStream.toByteArray()
}
@TypeConverter
fun toBitmap(byteArray: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
}

View File

@ -8,14 +8,10 @@ import androidx.room.PrimaryKey
@Entity(tableName = "authors") @Entity(tableName = "authors")
data class Author( data class Author(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
@ColumnInfo(name = "author_name") @ColumnInfo(name = "author_name")
val name: String val name: String
) { ) {
@Ignore
constructor(
name: String
) : this(null, name)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false

View File

@ -1,5 +1,6 @@
package com.example.myapplication.db.model package com.example.myapplication.db.model
import android.graphics.Bitmap
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
@ -20,14 +21,14 @@ import androidx.room.Ignore
entity = User::class, entity = User::class,
parentColumns = ["uid"], parentColumns = ["uid"],
childColumns = ["user_id"], childColumns = ["user_id"],
onDelete = ForeignKey.RESTRICT, onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.RESTRICT onUpdate = ForeignKey.CASCADE
) )
] ]
) )
data class Book( data class Book(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
@ColumnInfo(name = "title") @ColumnInfo(name = "title")
val title: String, val title: String,
@ColumnInfo(name = "description") @ColumnInfo(name = "description")
@ -35,21 +36,12 @@ data class Book(
@ColumnInfo(name = "content") @ColumnInfo(name = "content")
val content: String, val content: String,
@ColumnInfo(name = "cover") @ColumnInfo(name = "cover")
@DrawableRes val cover: Int?, val cover: Bitmap?,
@ColumnInfo(name = "author_id", index = true) @ColumnInfo(name = "author_id", index = true)
val authorId: Int?, val authorId: Int,
@ColumnInfo(name = "user_id", index = true) @ColumnInfo(name = "user_id", index = true)
val userId: Int? val userId: Int
) { ) {
@Ignore
constructor(
title: String,
description: String,
content: String,
cover: Int?,
authorId: Int?,
userId: Int?
) : this(null, title, description, content, cover, authorId, userId)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -9,7 +9,7 @@ import androidx.room.Index
@Entity(tableName = "users", indices = [(Index(value = ["login"], unique = true))]) @Entity(tableName = "users", indices = [(Index(value = ["login"], unique = true))])
data class User( data class User(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int?, val uid: Int = 0,
@ColumnInfo(name = "login") @ColumnInfo(name = "login")
var login: String, var login: String,
@ColumnInfo(name = "password") @ColumnInfo(name = "password")
@ -19,13 +19,6 @@ data class User(
@ColumnInfo(name = "admin") @ColumnInfo(name = "admin")
var role: String, var role: String,
) { ) {
@Ignore
constructor(
login: String,
password: String,
email: String,
role: String
) : this(null, login, password, email, role)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -0,0 +1,15 @@
package com.example.myapplication.db.respository
import androidx.paging.PagingData
import com.example.myapplication.db.model.Author
import kotlinx.coroutines.flow.Flow
interface AuthorRepository {
suspend fun getAll(): List<Author>
suspend fun getAllDrop(): List<Author>
suspend fun getByUid(uid: Int): Author
fun loadAllAuthorsPaged(): Flow<PagingData<Author>>
suspend fun insert(author: Author)
suspend fun update(author: Author)
suspend fun delete(author: Author)
}

View File

@ -0,0 +1,19 @@
package com.example.myapplication.db.respository
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.myapplication.db.model.Book
import com.example.myapplication.db.model.BookWithAuthor
import kotlinx.coroutines.flow.Flow
interface BookRepository {
suspend fun getAll(): List<Book>
suspend fun getByUid(uid: Int): Book
suspend fun getBySearch(searchStr: String): List<Book>
fun getByUserId(userId: Int): Flow<List<Book>>
fun getByAuthorId(authorId: Int): Flow<List<Book>>
fun loadAllBooksPaged(): Flow<PagingData<Book>>
suspend fun insert(book: Book)
suspend fun update(book: Book)
suspend fun delete(book: Book)
}

View File

@ -0,0 +1,29 @@
package com.example.myapplication.db.respository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.myapplication.db.database.AppContainer
import com.example.myapplication.db.dao.AuthorDao
import com.example.myapplication.db.model.Author
import kotlinx.coroutines.flow.Flow
class OfflineAuthorRepository(private val authorDao: AuthorDao) : AuthorRepository {
override suspend fun getAll(): List<Author> = authorDao.getAll()
override suspend fun getAllDrop(): List<Author> = authorDao.getAllDrop()
override suspend fun getByUid(uid: Int): Author = authorDao.getByUid(uid)
override fun loadAllAuthorsPaged(): Flow<PagingData<Author>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = authorDao::loadAllAuthorsPaged
).flow
fun loadAuthorsPaged(): PagingSource<Int, Author> = authorDao.loadAllAuthorsPaged()
override suspend fun insert(author: Author) = authorDao.insert(author)
override suspend fun update(author: Author) = authorDao.update(author)
override suspend fun delete(author: Author) = authorDao.delete(author)
}

View File

@ -0,0 +1,35 @@
package com.example.myapplication.db.respository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.myapplication.db.database.AppContainer
import com.example.myapplication.db.dao.BookDao
import com.example.myapplication.db.model.Book
import com.example.myapplication.db.model.BookWithAuthor
import kotlinx.coroutines.flow.Flow
class OfflineBookRepository(private val bookDao: BookDao) : BookRepository {
override suspend fun getAll(): List<Book> = bookDao.getAll()
override suspend fun getByUid(uid: Int): Book = bookDao.getByUid(uid)
override suspend fun getBySearch(searchStr: String): List<Book> = bookDao.getBySearch(searchStr)
override fun getByUserId(userId: Int): Flow<List<Book>> = bookDao.getByUserId(userId)
override fun getByAuthorId(authorId: Int): Flow<List<Book>> = bookDao.getByAuthorId(authorId)
override fun loadAllBooksPaged(): Flow<PagingData<Book>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = bookDao::loadAllBooksPaged
).flow
fun loadBooksPaged(): PagingSource<Int, Book> = bookDao.loadAllBooksPaged()
override suspend fun insert(book: Book) = bookDao.insert(book)
override suspend fun update(book: Book) = bookDao.update(book)
override suspend fun delete(book: Book) = bookDao.delete(book)
}

View File

@ -0,0 +1,22 @@
package com.example.myapplication.db.respository
import com.example.myapplication.db.dao.UserDao;
import com.example.myapplication.db.model.User;
import com.example.myapplication.db.model.Book;
import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAll(): Flow<List<User>> = userDao.getAll()
override suspend fun getByUid(uid: Int): User = userDao.getByUid(uid)
override fun getUserBooks(userid: Int): Flow<List<Book>> = userDao.getUserBooks(userid)
override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password)
override suspend fun insert(user: User) = userDao.insert(user)
override suspend fun update(user: User) = userDao.update(user)
override suspend fun delete(user: User) = userDao.delete(user)
}

View File

@ -0,0 +1,14 @@
package com.example.myapplication.db.respository
import com.example.myapplication.db.model.User;
import com.example.myapplication.db.model.Book;
import kotlinx.coroutines.flow.Flow
interface UserRepository {
fun getAll(): Flow<List<User>>
suspend fun getByUid(uid: Int): User
fun getUserBooks(userid: Int): Flow<List<Book>>
suspend fun tryLogin(login: String, password: String): User?
suspend fun insert(user: User)
suspend fun update(user: User)
suspend fun delete(user: User)
}

View File

@ -17,9 +17,9 @@ import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme( private val DarkColorScheme = darkColorScheme(
primary = Purple80, primary = Green,
secondary = PurpleGrey80, secondary = Grey,
tertiary = Pink80 tertiary = Pink40,
) )
private val LightColorScheme = lightColorScheme( private val LightColorScheme = lightColorScheme(

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB