что происходит с картинками(

This commit is contained in:
ityurner02@mail.ru 2023-12-20 21:32:03 +04:00
parent 1dc8b51c03
commit 0374fb1c29
48 changed files with 3963 additions and 116 deletions

1642
Server/data.json Normal file

File diff suppressed because it is too large Load Diff

1335
Server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

12
Server/package.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "fake-db",
"version": "1.0.0",
"scripts": {
"start": "json-server --watch data.json --host 0.0.0.0 -p 8079"
},
"dependencies": {
},
"devDependencies": {
"json-server": "0.17.4"
}
}

View File

@ -2,6 +2,7 @@ plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp") id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
} }
android { android {
@ -59,6 +60,8 @@ dependencies {
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3") implementation("androidx.compose.material3:material3")
implementation("dev.materii.pullrefresh:pullrefresh:1.0.0")
val room_version = "2.5.2" val room_version = "2.5.2"
implementation("androidx.room:room-runtime:$room_version") implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version") annotationProcessor("androidx.room:room-compiler:$room_version")
@ -78,4 +81,14 @@ dependencies {
val paging_version = "3.2.0-rc01" val paging_version = "3.2.0-rc01"
implementation("androidx.paging:paging-runtime:$paging_version") implementation("androidx.paging:paging-runtime:$paging_version")
implementation("androidx.paging:paging-compose:$paging_version") implementation("androidx.paging:paging-compose:$paging_version")
// retrofit
val retrofitVersion = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
// LiveData | +
implementation("androidx.compose.runtime:runtime-livedata:1.5.4")
} }

View File

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:name=".MyApplication" android:name=".MyApplication"
android:allowBackup="true" android:allowBackup="true"
@ -12,8 +14,11 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.MyApplication" android:theme="@style/Theme.MyApplication"
tools:targetApi="31"> tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config"
android:vmSafeMode="true">
<activity <activity
android:screenOrientation="portrait"
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"

View File

@ -0,0 +1,143 @@
package com.example.myapplication.api
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.example.myapplication.api.model.AuthorRemote
import com.example.myapplication.api.model.BookRemote
import com.example.myapplication.api.model.UserRemote
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
interface ServerService{
@GET("books")
suspend fun getBooks(): List<BookRemote>
@GET("books/{id}")
suspend fun getBook(
@Path("id") id: Int,
): BookRemote
@GET("search")
suspend fun getBySearch(
@Query("title") searchStr: String,
): List<BookRemote>
@GET("books")
suspend fun getByAuthor(
@Query("authorId") auth: Int,
): List<BookRemote>
@GET("books")
suspend fun getByUser(
@Query("userId") user: Int,
): List<BookRemote>
@GET("books")
suspend fun getBooks(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<BookRemote>
@POST("books")
suspend fun createBook(
@Body book: BookRemote,
): BookRemote
@PUT("books/{id}")
suspend fun updateBook(
@Path("id") id: Int,
@Body book: BookRemote,
): BookRemote
@DELETE("books/{id}")
suspend fun deleteBook(
@Path("id") id: Int,
): BookRemote
@GET("authors")
suspend fun getAuthors(): List<AuthorRemote>
@GET("authors")
suspend fun getAuthors(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<AuthorRemote>
@GET("authors/{id}")
suspend fun getAuthor(
@Path("id") id: Int,
): AuthorRemote
@POST("authors")
suspend fun createAuthor(
@Body author: AuthorRemote,
): AuthorRemote
@PUT("authors/{id}")
suspend fun updateAuthor(
@Path("id") id: Int,
@Body author: AuthorRemote,
): AuthorRemote
@DELETE("authors/{id}")
suspend fun deleteAuthor(
@Path("id") id: Int,
): AuthorRemote
@GET("users")
suspend fun getUsers(): List<UserRemote>
@GET("users/{id}")
suspend fun getUser(
@Path("id") id: Int,
): UserRemote
@GET("users")
suspend fun getUserByLoginPass(
@Query("login") login: String,
@Query("password") password: String
): UserRemote
@POST("users")
suspend fun createUser(
@Body user: UserRemote,
): UserRemote
@PUT("users/{id}")
suspend fun updateUser(
@Path("id") id: Int,
@Body user: UserRemote,
): UserRemote
@DELETE("users/{id}")
suspend fun deleteUser(
@Path("id") id: Int,
): UserRemote
companion object {
private const val BASE_URL = "http://100.87.48.148:8079/"
@Volatile
private var INSTANCE: ServerService? = null
fun getInstance(): ServerService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
val json = Json { ignoreUnknownKeys = true } // Создаем экземпляр Json с ignoreUnknownKeys = true
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) // Применяем конфигурацию Json
.build()
.create(ServerService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,38 @@
package com.example.myapplication.api.model
import com.example.myapplication.db.model.Author
import kotlinx.serialization.Serializable
@Serializable
data class AuthorRemote(
val id: Int = 0,
val name: String = "",
){
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AuthorRemote
if (id != other.id) return false
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
var result = id
result = 31 * result + name.hashCode()
return result
}
}
fun AuthorRemote.toAuthor(): Author = Author(
id,
name
)
fun Author.toAuthorRemote(): AuthorRemote = AuthorRemote(
id,
name
)

View File

@ -0,0 +1,68 @@
package com.example.myapplication.api.model
import com.example.myapplication.db.database.Converters
import com.example.myapplication.db.model.Book
import kotlinx.serialization.Serializable
private val __converters = Converters()
@Serializable
data class BookRemote(
val id: Int = 0,
val title: String = "",
val description: String = "",
val content: String = "",
val cover: ByteArray? = null,
val authorId: Int = 0,
val userId: Int = 0
){
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as BookRemote
if (id != other.id) return false
if (title != other.title) return false
if (description != other.description) return false
if (content != other.content) return false
if (cover != null) {
if (other.cover == null) return false
if (!cover.contentEquals(other.cover)) return false
} else if (other.cover != null) return false
if (authorId != other.authorId) return false
if (userId != other.userId) return false
return true
}
override fun hashCode(): Int {
var result = id
result = 31 * result + title.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + content.hashCode()
result = 31 * result + (cover?.contentHashCode() ?: 0)
result = 31 * result + authorId
result = 31 * result + userId
return result
}
}
fun BookRemote.toBook(): Book = Book(
id,
title,
description,
content,
cover?.let { __converters.toBitmap(it) },
authorId,
userId
)
fun Book.toBookRemote(): BookRemote = BookRemote(
id,
title,
description,
content,
cover?.let { __converters.fromBitmap(it) },
authorId,
userId
)

View File

@ -0,0 +1,54 @@
package com.example.myapplication.api.model
import com.example.myapplication.db.model.User
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
val id: Int = 0,
val login: String = "",
val password: String = "",
val email: String = "",
val role: String = ""
){
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as UserRemote
if (id != other.id) return false
if (login != other.login) return false
if (password != other.password) return false
if (email != other.email) return false
if (role != other.role) return false
return true
}
override fun hashCode(): Int {
var result = id
result = 31 * result + login.hashCode()
result = 31 * result + password.hashCode()
result = 31 * result + email.hashCode()
result = 31 * result + role.hashCode()
return result
}
}
fun UserRemote.toUser(): User = User(
id,
login,
password,
email,
role
)
fun User.toUserRemote(): UserRemote = UserRemote(
uid,
login,
password,
email,
role
)

View File

@ -0,0 +1,107 @@
package com.example.myapplication.api.respositories.Mediator
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.model.toAuthor
import com.example.myapplication.db.database.AppDatabase
import com.example.myapplication.db.model.Author
import com.example.myapplication.db.model.RemoteKeyType
import com.example.myapplication.db.model.RemoteKeys
import com.example.myapplication.db.respository.OfflineAuthorRepository
import com.example.myapplication.db.respository.OfflineBookRepository
import com.example.myapplication.db.respository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class AuthorRemoteMediator(
private val service: ServerService,
private val dbAuthorRepository: OfflineAuthorRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
): RemoteMediator<Int, Author>() {
override suspend fun initialize(): RemoteMediator.InitializeAction {
return RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Author>
): RemoteMediator.MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return RemoteMediator.MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return RemoteMediator.MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val authors = service.getAuthors(page, state.config.pageSize).map{it.toAuthor()}
Log.i("Authors info", authors.toString())
val endOfPaginationReached = authors.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.AUTHOR)
dbAuthorRepository.clearAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = authors.map {
RemoteKeys(
entityId = it.id,
type = RemoteKeyType.AUTHOR,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbAuthorRepository.insertAuthors(authors)
}
return RemoteMediator.MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return RemoteMediator.MediatorResult.Error(exception)
} catch (exception: HttpException) {
return RemoteMediator.MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Author>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { author ->
dbRemoteKeyRepository.getAllRemoteKeys(author.id, RemoteKeyType.AUTHOR)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Author>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { author ->
dbRemoteKeyRepository.getAllRemoteKeys(author.id, RemoteKeyType.AUTHOR)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Author>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { authorUid ->
dbRemoteKeyRepository.getAllRemoteKeys(authorUid, RemoteKeyType.AUTHOR)
}
}
}
}

View File

@ -0,0 +1,110 @@
package com.example.myapplication.api.respositories.Mediator
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.model.toBook
import com.example.myapplication.api.respositories.RestAuthorRepository
import com.example.myapplication.db.database.AppDatabase
import com.example.myapplication.db.model.Book
import com.example.myapplication.db.model.RemoteKeyType
import com.example.myapplication.db.model.RemoteKeys
import com.example.myapplication.db.respository.OfflineBookRepository
import com.example.myapplication.db.respository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class BookRemoteMediator(
private val service: ServerService,
private val dbBookRepository: OfflineBookRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val authorRestRepository: RestAuthorRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Book>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Book>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val books = service.getBooks(page, state.config.pageSize).map { it.toBook() }
Log.i("Books info", books.toString())
val endOfPaginationReached = books.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.BOOK)
dbBookRepository.clearAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = books.map {
RemoteKeys(
entityId = it.id,
type = RemoteKeyType.BOOK,
prevKey = prevKey,
nextKey = nextKey
)
}
authorRestRepository.getAll()
dbRemoteKeyRepository.createRemoteKeys(keys)
dbBookRepository.insertBooks(books)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Book>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { book ->
dbRemoteKeyRepository.getAllRemoteKeys(book.id, RemoteKeyType.BOOK)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Book>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { book ->
dbRemoteKeyRepository.getAllRemoteKeys(book.id, RemoteKeyType.BOOK)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Book>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { bookUid ->
dbRemoteKeyRepository.getAllRemoteKeys(bookUid, RemoteKeyType.BOOK)
}
}
}
}

View File

@ -0,0 +1,72 @@
package com.example.myapplication.api.respositories
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.api.ServerService
import com.example.myapplication.db.database.AppDatabase
import com.example.myapplication.db.database.AppContainer
import com.example.myapplication.db.respository.OfflineAuthorRepository
import com.example.myapplication.db.respository.OfflineRemoteKeyRepository
import com.example.myapplication.db.respository.AuthorRepository
import com.example.myapplication.api.model.toAuthor
import com.example.myapplication.api.model.toAuthorRemote
import com.example.myapplication.db.model.Author
import com.example.myapplication.api.respositories.Mediator.AuthorRemoteMediator
import kotlinx.coroutines.flow.Flow
class RestAuthorRepository(
private val service: ServerService,
private val dbAuthorRepository: OfflineAuthorRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
): AuthorRepository {
override suspend fun getAll(): List<Author> {
return service.getAuthors().map{x -> x.toAuthor()}
}
override suspend fun getAllDrop(): List<Author> {
return service.getAuthors().map{x -> x.toAuthor()}
}
override suspend fun getByUid(uid: Int): Author {
return service.getAuthor(uid).toAuthor()
}
override suspend fun insertAuthor(author: Author) {
dbAuthorRepository.insertAuthor(service.createAuthor(author.toAuthorRemote()).toAuthor())
}
override suspend fun updateAuthor(author: Author) {
service.updateAuthor(author.id, author.toAuthorRemote())
dbAuthorRepository.updateAuthor(author)
}
override suspend fun deleteAuthor(author: Author) {
service.deleteAuthor(author.id)
dbAuthorRepository.deleteAuthor(author)
}
override fun loadAllAuthorsPaged(): Flow<PagingData<Author>> {
Log.d(RestAuthorRepository::class.simpleName, "Get authors")
val pagingSourceFactory = { dbAuthorRepository.loadAuthorsPaged() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = AuthorRemoteMediator(
service,
dbAuthorRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}

View File

@ -0,0 +1,84 @@
package com.example.myapplication.api.respositories
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.myapplication.api.ServerService
import com.example.myapplication.db.respository.OfflineBookRepository
import com.example.myapplication.db.respository.OfflineRemoteKeyRepository
import com.example.myapplication.db.database.AppDatabase
import com.example.myapplication.db.respository.BookRepository
import com.example.myapplication.api.model.toBook
import com.example.myapplication.api.model.toBookRemote
import com.example.myapplication.api.respositories.Mediator.BookRemoteMediator
import com.example.myapplication.db.database.AppContainer
import com.example.myapplication.db.model.Book
import kotlinx.coroutines.flow.Flow
class RestBookRepository(
private val service: ServerService,
private val dbBookRepository: OfflineBookRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val authorRestRepository: RestAuthorRepository,
private val database: AppDatabase
):BookRepository {
override suspend fun getAll(): List<Book> {
return service.getBooks().map{x -> x.toBook()}
}
override suspend fun getByUid(uid: Int): Book {
return service.getBook(uid).toBook()
}
override suspend fun getBySearch(searchStr: String): List<Book> {
return service.getBySearch(searchStr).map{x -> x.toBook()}
}
override suspend fun getByAuthorId(authorId: Int): List<Book> {
return service.getByAuthor(authorId).map{ x -> x.toBook()}
}
override suspend fun getByUserId(userId: Int): List<Book> {
return service.getByUser(userId).map{ x -> x.toBook()}
}
override fun loadAllBooksPaged(): Flow<PagingData<Book>> {
Log.d(RestBookRepository::class.simpleName, "Get books")
val pagingSourceFactory = { dbBookRepository.loadBooksPaged() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = BookRemoteMediator(
service,
dbBookRepository,
dbRemoteKeyRepository,
authorRestRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun insertBook(book: Book) {
dbBookRepository.insertBook(service.createBook(book.toBookRemote()).toBook())
}
override suspend fun updateBook(book: Book) {
service.updateBook(book.id, book.toBookRemote())
dbBookRepository.updateBook(book)
}
override suspend fun deleteBook(book: Book) {
service.deleteBook(book.id)
dbBookRepository.deleteBook(book)
}
}

View File

@ -0,0 +1,47 @@
package com.example.myapplication.api.respositories
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.model.toUser
import com.example.myapplication.api.model.toBook
import com.example.myapplication.api.model.toUserRemote
import com.example.myapplication.db.database.AppDatabase
import com.example.myapplication.db.model.Book
import com.example.myapplication.db.model.User
import com.example.myapplication.db.respository.OfflineUserRepository
import com.example.myapplication.db.respository.UserRepository
class RestUserRepository(
private val service: ServerService,
private val dbUserRepository: OfflineUserRepository,
private val database: AppDatabase
): UserRepository {
override suspend fun getAll(): List<User> {
return service.getUsers().map{x -> x.toUser()}
}
override suspend fun getByUid(uid: Int): User {
return service.getUser(uid).toUser()
}
override suspend fun getUserBooks(userid: Int): List<Book> {
return service.getByUser(userid).map{x -> x.toBook()}
}
override suspend fun tryLogin(login: String, password: String): User? {
return service.getUserByLoginPass(login, password).toUser()
}
override suspend fun insert(user: User) {
dbUserRepository.insert(service.createUser(user.toUserRemote()).toUser())
}
override suspend fun update(user: User) {
service.updateUser(user.uid, user.toUserRemote())
dbUserRepository.update(user)
}
override suspend fun delete(user: User) {
service.deleteUser(user.uid)
dbUserRepository.delete(user)
}
}

View File

@ -21,52 +21,52 @@ object AppViewModelProvider {
initializer { initializer {
UserPageViewModel( UserPageViewModel(
this.createSavedStateHandle(), this.createSavedStateHandle(),
myApplication().container.userRepository myApplication().container.userRestRepository
) )
} }
initializer { initializer {
UserEditViewModel( UserEditViewModel(
this.createSavedStateHandle(), this.createSavedStateHandle(),
myApplication().container.userRepository myApplication().container.userRestRepository
) )
} }
initializer { initializer {
SearchPageViewModel( SearchPageViewModel(
myApplication().container.bookRepository, myApplication().container.bookRestRepository,
this.createSavedStateHandle() this.createSavedStateHandle()
) )
} }
initializer { initializer {
BookPageViewModel( BookPageViewModel(
myApplication().container.bookRepository, myApplication().container.bookRestRepository,
this.createSavedStateHandle() this.createSavedStateHandle()
) )
} }
initializer { initializer {
BookListViewModel( BookListViewModel(
myApplication().container.bookRepository myApplication().container.bookRestRepository
) )
} }
initializer { initializer {
BookEditViewModel( BookEditViewModel(
this.createSavedStateHandle(), this.createSavedStateHandle(),
myApplication().container.bookRepository myApplication().container.bookRestRepository
) )
} }
initializer { initializer {
AuthorListViewModel( AuthorListViewModel(
myApplication().container.authorRepository myApplication().container.authorRestRepository
) )
} }
initializer { initializer {
AuthorEditViewModel( AuthorEditViewModel(
this.createSavedStateHandle(), this.createSavedStateHandle(),
myApplication().container.authorRepository myApplication().container.authorRestRepository
) )
} }
initializer { initializer {
AuthorDropDownViewModel( AuthorDropDownViewModel(
myApplication().container.authorRepository myApplication().container.authorRestRepository
) )
} }
} }

View File

@ -56,7 +56,7 @@ fun AuthorCell(navController: NavController?, author: Author, viewModel: AuthorL
modifier = Modifier modifier = Modifier
.padding(all = 5.dp), .padding(all = 5.dp),
onClick = { onClick = {
val route = Screen.AuthorEdit.route.replace("{id}", author.uid.toString()) val route = Screen.AuthorEdit.route.replace("{id}", author.id.toString())
navController!!.navigate(route) navController!!.navigate(route)
}, },
) { ) {
@ -71,16 +71,7 @@ fun AuthorCell(navController: NavController?, author: Author, viewModel: AuthorL
.padding(all = 5.dp), .padding(all = 5.dp),
onClick = { onClick = {
scope.launch { scope.launch {
if (AppDatabase.getInstance(context).bookDao().getByAuthorId(author.uid).firstOrNull().toString() == "[]") {
viewModel.deleteAuthor(author) viewModel.deleteAuthor(author)
} else {
val toast = Toast.makeText(
MainActivity.appContext,
"Невозможно удалить, есть книги данного автора",
Toast.LENGTH_SHORT
)
toast.show()
}
} }
}, },
colors = ButtonDefaults.buttonColors(containerColor = Color.Red), colors = ButtonDefaults.buttonColors(containerColor = Color.Red),

View File

@ -46,7 +46,7 @@ fun BookCell(navController: NavController?, book: Book, viewModel: BookListViewM
navController?.navigate( navController?.navigate(
Screen.BookView.route.replace( Screen.BookView.route.replace(
"{id}", "{id}",
book.uid.toString() book.id.toString()
) )
) )
}) })
@ -59,7 +59,7 @@ fun BookCell(navController: NavController?, book: Book, viewModel: BookListViewM
navController?.navigate( navController?.navigate(
Screen.BookView.route.replace( Screen.BookView.route.replace(
"{id}", "{id}",
book.uid.toString() book.id.toString()
) )
) )
}) { }) {
@ -79,7 +79,7 @@ fun BookCell(navController: NavController?, book: Book, viewModel: BookListViewM
modifier = Modifier modifier = Modifier
.padding(all = 5.dp), .padding(all = 5.dp),
onClick = { onClick = {
val route = Screen.BookEdit.route.replace("{id}", book.uid.toString()) val route = Screen.BookEdit.route.replace("{id}", book.id.toString())
navController!!.navigate(route) navController!!.navigate(route)
}, },
) { ) {

View File

@ -144,7 +144,7 @@ private fun BookEdit(
authorUiState = authorUiState, authorUiState = authorUiState,
authorsListUiState = authorsListUiState, authorsListUiState = authorsListUiState,
onAuthorUpdate = { onAuthorUpdate = {
onUpdate(bookUiState.bookDetails.copy(authorId = it.uid)) onUpdate(bookUiState.bookDetails.copy(authorId = it.id))
onAuthorUpdate(it) onAuthorUpdate(it)
} }
) )

View File

@ -46,9 +46,6 @@ private fun getBookImage(context: Context, imageId: String): Bitmap {
fun BookView(navController: NavController, id: Int, viewModel: BookPageViewModel = viewModel(factory = AppViewModelProvider.Factory)) { fun BookView(navController: NavController, id: Int, viewModel: BookPageViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val book = viewModel.bookPageUiState val book = viewModel.bookPageUiState
LaunchedEffect(Unit) {
viewModel.refreshState()
}
val context = LocalContext.current val context = LocalContext.current
Column( Column(
Modifier Modifier

View File

@ -29,6 +29,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.example.myapplication.MainActivity import com.example.myapplication.MainActivity
import com.example.myapplication.SingletonClass import com.example.myapplication.SingletonClass
import com.example.myapplication.api.ServerService
import com.example.myapplication.composeui.navigation.Screen import com.example.myapplication.composeui.navigation.Screen
import com.example.myapplication.db.database.AppDatabase import com.example.myapplication.db.database.AppDatabase
import com.example.myapplication.db.model.User import com.example.myapplication.db.model.User
@ -44,6 +45,7 @@ fun Enter(navController: NavController) {
var SingletonClass = SingletonClass() var SingletonClass = SingletonClass()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
val service: ServerService
var login by remember{mutableStateOf("")} var login by remember{mutableStateOf("")}
var password by remember{mutableStateOf("")} var password by remember{mutableStateOf("")}
Column( Column(
@ -65,15 +67,17 @@ fun Enter(navController: NavController) {
.fillMaxWidth() .fillMaxWidth()
.padding(all = 10.dp), .padding(all = 10.dp),
onClick = { onClick = {
scope.launch {if( AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString()) != null){ scope.launch {
SingletonClass.setUserId(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.uid!!) //if( AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString()) != null){
SingletonClass.setRole(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.role!!) // SingletonClass.setUserId(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.uid!!)
//SingletonClass.setRole(AppDatabase.getInstance(context).userDao().tryLogin(login.toString(), password.toString())?.role!!)
navController?.navigate(Screen.Profile.route) navController?.navigate(Screen.Profile.route)
//}
//else{
// val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT)
// toast.show()
//}
} }
else{
val toast = Toast.makeText(MainActivity.appContext, "Неверный логин или пароль", Toast.LENGTH_SHORT)
toast.show()
}}
}) { }) {
Text(stringResource(id = R.string.enter)) Text(stringResource(id = R.string.enter))
} }

View File

@ -1,42 +1,24 @@
package com.example.myapplication.composeui 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.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
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.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController 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.UserDetails
import com.example.myapplication.composeui.ViewModel.UserEditViewModel import com.example.myapplication.composeui.ViewModel.UserEditViewModel
import com.example.myapplication.composeui.ViewModel.UserPageViewModel
import com.example.myapplication.composeui.ViewModel.UserUiState 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 import kotlinx.coroutines.launch
@Composable @Composable

View File

@ -26,7 +26,7 @@ class AuthorDropDownViewModel(
fun setCurrentAuthor(authorId: Int) { fun setCurrentAuthor(authorId: Int) {
val author: Author? = val author: Author? =
authorsListUiState.authorList.firstOrNull { author -> author.uid == authorId } authorsListUiState.authorList.firstOrNull { author -> author.id == authorId }
author?.let { updateUiState(it) } author?.let { updateUiState(it) }
} }
@ -43,4 +43,4 @@ data class AuthorsUiState(
val author: Author? = null val author: Author? = null
) )
fun Author.toUiState() = AuthorsUiState(author = Author(uid = uid, name = name)) fun Author.toUiState() = AuthorsUiState(author = Author(id = id, name = name))

View File

@ -36,9 +36,9 @@ class AuthorEditViewModel(savedStateHandle: SavedStateHandle,
suspend fun saveAuthor() { suspend fun saveAuthor() {
if (validateInput()) { if (validateInput()) {
if (authorUid > 0) { if (authorUid > 0) {
authorRepository.update(authorUiState.authorDetails.toAuthor(authorUid)) authorRepository.updateAuthor(authorUiState.authorDetails.toAuthor(authorUid))
} else { } else {
authorRepository.insert(authorUiState.authorDetails.toAuthor()) authorRepository.insertAuthor(authorUiState.authorDetails.toAuthor())
} }
} }
} }
@ -57,7 +57,7 @@ data class AuthorDetails(
val name: String = "" val name: String = ""
) )
fun AuthorDetails.toAuthor(uid: Int = 0): Author = Author( fun AuthorDetails.toAuthor(uid: Int = 0): Author = Author(
uid = uid, id = uid,
name = name name = name
) )

View File

@ -26,7 +26,7 @@ class AuthorListViewModel(
} }
val authorPagedData: Flow<PagingData<Author>> = authorRepository.loadAllAuthorsPaged() val authorPagedData: Flow<PagingData<Author>> = authorRepository.loadAllAuthorsPaged()
suspend fun deleteAuthor(author: Author) { suspend fun deleteAuthor(author: Author) {
authorRepository.delete(author) authorRepository.deleteAuthor(author)
} }
} }
data class AuthorListUiState(val authorList: List<Author> = listOf()) data class AuthorListUiState(val authorList: List<Author> = listOf())

View File

@ -36,9 +36,9 @@ class BookEditViewModel(savedStateHandle: SavedStateHandle,
suspend fun saveBook() { suspend fun saveBook() {
if (validateInput()) { if (validateInput()) {
if (bookUid > 0) { if (bookUid > 0) {
bookRepository.update(bookUiState.bookDetails.toBook(bookUid)) bookRepository.updateBook(bookUiState.bookDetails.toBook(bookUid))
} else { } else {
bookRepository.insert(bookUiState.bookDetails.toBook()) bookRepository.insertBook(bookUiState.bookDetails.toBook())
} }
} }
} }
@ -67,7 +67,7 @@ data class BookDetails(
val userId: Int = 0, val userId: Int = 0,
) )
fun BookDetails.toBook(uid: Int = 0): Book = Book( fun BookDetails.toBook(uid: Int = 0): Book = Book(
uid = uid, id = uid,
title = title, title = title,
description = description, description = description,
content = content, content = content,

View File

@ -26,7 +26,7 @@ class BookListViewModel(
} }
val bookPagedData: Flow<PagingData<Book>> = bookRepository.loadAllBooksPaged() val bookPagedData: Flow<PagingData<Book>> = bookRepository.loadAllBooksPaged()
suspend fun deleteBook(book: Book) { suspend fun deleteBook(book: Book) {
bookRepository.delete(book) bookRepository.deleteBook(book)
} }
} }

View File

@ -9,6 +9,16 @@ import androidx.lifecycle.SavedStateHandle
import com.example.myapplication.db.model.Book import com.example.myapplication.db.model.Book
import com.example.myapplication.db.model.BookWithAuthor import com.example.myapplication.db.model.BookWithAuthor
import com.example.myapplication.db.respository.BookRepository import com.example.myapplication.db.respository.BookRepository
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope
import com.example.myapplication.db.database.AppContainer
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class BookPageViewModel(private val bookRespository: BookRepository, savedStateHandle: SavedStateHandle) : ViewModel(){ class BookPageViewModel(private val bookRespository: BookRepository, savedStateHandle: SavedStateHandle) : ViewModel(){
@ -26,7 +36,7 @@ class BookPageViewModel(private val bookRespository: BookRepository, savedStateH
bookPageUiState = BookPageUiState(bookRespository.getByUid(bookId)) bookPageUiState = BookPageUiState(bookRespository.getByUid(bookId))
} }
suspend fun deleteBook(book: Book) { suspend fun deleteBook(book: Book) {
bookRespository.delete(book) bookRespository.deleteBook(book)
} }
} }

View File

@ -7,10 +7,13 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.filter
import com.example.myapplication.db.model.Book import com.example.myapplication.db.model.Book
import com.example.myapplication.db.respository.BookRepository import com.example.myapplication.db.respository.BookRepository
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.util.Locale
class SearchPageViewModel(private val bookRepository: BookRepository, savedStateHandle: SavedStateHandle) : ViewModel(){ class SearchPageViewModel(private val bookRepository: BookRepository, savedStateHandle: SavedStateHandle) : ViewModel(){
private val searchStr: String = checkNotNull(savedStateHandle["searchStr"]) private val searchStr: String = checkNotNull(savedStateHandle["searchStr"])
@ -25,7 +28,11 @@ class SearchPageViewModel(private val bookRepository: BookRepository, savedState
searchPageUiState = SearchPageUiState(bookRepository.getBySearch(searchStr)) searchPageUiState = SearchPageUiState(bookRepository.getBySearch(searchStr))
} }
val bookPagedData: Flow<PagingData<Book>> = bookRepository.loadAllBooksPaged() val bookPagedData: Flow<PagingData<Book>> = bookRepository.loadAllBooksPaged().map{
x -> x.filter{
y-> (y.title.lowercase(Locale.ROOT)).contains(searchStr.lowercase(Locale.ROOT))
}
}
} }
data class SearchPageUiState(val bookList: List<Book> = listOf()) data class SearchPageUiState(val bookList: List<Book> = listOf())

View File

@ -21,7 +21,7 @@ class UserEditViewModel(savedStateHandle: SavedStateHandle,
init { init {
viewModelScope.launch { viewModelScope.launch {
if (userUid > 0) { if (userUid > 0) {
userUiState = userRepository.getByUid(userUid) userUiState = userRepository.getByUid(userUid)!!
.toUiState(true) .toUiState(true)
} }
} }

View File

@ -15,13 +15,15 @@ interface AuthorDao {
suspend fun getAll(): List<Author> suspend fun getAll(): List<Author>
@Query("select * from authors order by author_name collate nocase asc") @Query("select * from authors order by author_name collate nocase asc")
suspend fun getAllDrop(): List<Author> suspend fun getAllDrop(): List<Author>
@Query("select * from authors where authors.uid = :uid") @Query("select * from authors where authors.id = :uid")
suspend fun getByUid(uid: Int): Author suspend fun getByUid(uid: Int): Author
@Query("select * from authors order by author_name collate nocase asc") @Query("select * from authors order by author_name collate nocase asc")
fun loadAllAuthorsPaged(): PagingSource<Int, Author> fun loadAllAuthorsPaged(): PagingSource<Int, Author>
@Query("DELETE FROM authors")
suspend fun clearAll()
@Insert @Insert
suspend fun insert(author: Author) suspend fun insert(vararg author: Author)
@Update @Update
suspend fun update(author: Author) suspend fun update(author: Author)

View File

@ -15,23 +15,25 @@ interface BookDao {
@Query("select * from books order by title collate nocase asc") @Query("select * from books order by title collate nocase asc")
suspend fun getAll(): List<Book> suspend fun getAll(): List<Book>
@Query("select * from books where books.uid = :uid") @Query("select * from books where books.id = :uid")
suspend fun getByUid(uid: Int): Book suspend fun getByUid(uid: Int): Book
@Query("select * from books where books.title LIKE '%' || :searchStr || '%'") @Query("select * from books where books.title LIKE '%' || :searchStr || '%'")
suspend fun getBySearch(searchStr: String): 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>> suspend fun getByUserId(userId: Int): List<Book>
@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>> suspend fun getByAuthorId(authorId: Int): List<Book>
@Query("select * from books order by title collate nocase asc") @Query("select * from books order by title collate nocase asc")
fun loadAllBooksPaged(): PagingSource<Int, Book> fun loadAllBooksPaged(): PagingSource<Int, Book>
@Query("DELETE FROM books")
suspend fun clearAll()
@Insert @Insert
suspend fun insert(book: Book) suspend fun insert(vararg book: Book)
@Update @Update
suspend fun update(book: Book) suspend fun update(book: Book)

View File

@ -0,0 +1,20 @@
package com.example.myapplication.db.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.example.myapplication.db.model.RemoteKeyType
import com.example.myapplication.db.model.RemoteKeys
@Dao
interface RemoteKeysDao {
@Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type")
suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys>)
@Query("DELETE FROM remote_keys WHERE type = :type")
suspend fun clearRemoteKeys(type: RemoteKeyType)
}

View File

@ -12,13 +12,13 @@ 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>> suspend fun getAll(): List<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") @Query("select * from books where books.user_id = :userid")
fun getUserBooks(userid: Int): Flow<List<Book>> suspend fun getUserBooks(userid: Int): List<Book>
@Query("select * from users where login = :login and password = :password") @Query("select * from users where login = :login and password = :password")
suspend fun tryLogin(login: String, password: String): User? suspend fun tryLogin(login: String, password: String): User?

View File

@ -9,12 +9,17 @@ import com.example.myapplication.db.respository.AuthorRepository
import com.example.myapplication.db.respository.UserRepository import com.example.myapplication.db.respository.UserRepository
import com.example.myapplication.db.respository.OfflineBookRepository import com.example.myapplication.db.respository.OfflineBookRepository
import com.example.myapplication.db.respository.OfflineAuthorRepository import com.example.myapplication.db.respository.OfflineAuthorRepository
import com.example.myapplication.db.respository.OfflineRemoteKeyRepository
import com.example.myapplication.db.respository.OfflineUserRepository import com.example.myapplication.db.respository.OfflineUserRepository
import com.example.myapplication.api.respositories.RestBookRepository
import com.example.myapplication.api.respositories.RestAuthorRepository
import com.example.myapplication.api.respositories.RestUserRepository
import com.example.myapplication.api.ServerService
interface AppContainer { interface AppContainer {
val bookRepository: BookRepository val bookRestRepository: RestBookRepository
val authorRepository: AuthorRepository val authorRestRepository: RestAuthorRepository
val userRepository: UserRepository val userRestRepository: RestUserRepository
companion object { companion object {
const val TIMEOUT = 5000L const val TIMEOUT = 5000L
const val LIMIT = 10 const val LIMIT = 10
@ -22,15 +27,44 @@ interface AppContainer {
} }
class AppDataContainer(private val context: Context) : AppContainer { class AppDataContainer(private val context: Context) : AppContainer {
override val bookRepository: BookRepository by lazy { private val bookRepository: OfflineBookRepository by lazy {
OfflineBookRepository(AppDatabase.getInstance(context).bookDao()) OfflineBookRepository(AppDatabase.getInstance(context).bookDao())
} }
override val authorRepository: AuthorRepository by lazy { private val authorRepository: OfflineAuthorRepository by lazy {
OfflineAuthorRepository(AppDatabase.getInstance(context).authorDao()) OfflineAuthorRepository(AppDatabase.getInstance(context).authorDao())
} }
override val userRepository: UserRepository by lazy { private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao()) OfflineUserRepository(AppDatabase.getInstance(context).userDao())
} }
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
}
override val bookRestRepository: RestBookRepository by lazy {
RestBookRepository(
ServerService.getInstance(),
bookRepository,
remoteKeyRepository,
authorRestRepository,
AppDatabase.getInstance(context)
)
}
override val authorRestRepository: RestAuthorRepository by lazy {
RestAuthorRepository(
ServerService.getInstance(),
authorRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val userRestRepository: RestUserRepository by lazy{
RestUserRepository(
ServerService.getInstance(),
userRepository,
AppDatabase.getInstance(context)
)
}
companion object { companion object {
const val TIMEOUT = 5000L const val TIMEOUT = 5000L

View File

@ -18,23 +18,26 @@ import com.example.myapplication.db.model.User;
import com.example.myapplication.db.model.Book; import com.example.myapplication.db.model.Book;
import com.example.myapplication.db.model.Author; import com.example.myapplication.db.model.Author;
import com.example.myapplication.R import com.example.myapplication.R
import com.example.myapplication.db.dao.RemoteKeysDao
import com.example.myapplication.db.model.RemoteKeys
@Database(entities = [Book::class, Author::class, User::class], version = 1, exportSchema = false) @Database(entities = [Book::class, Author::class, User::class, RemoteKeys::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class) @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
abstract fun remoteKeysDao(): RemoteKeysDao
companion object { companion object {
private const val DB_NAME: String = "PMU1" private const val DB_NAME: String = "PMU4"
@Volatile @Volatile
private var INSTANCE: AppDatabase? = null private var INSTANCE: AppDatabase? = null
private suspend fun populateDatabase(context: Context) { private suspend fun populateDatabase(context: Context) {
INSTANCE?.let { database -> INSTANCE?.let { database ->
//Users /* //Users
val userDao = database.userDao() val userDao = database.userDao()
val user1 = User(1, "Admin", "Admin", "admin@mail.ru", "ADMIN") val user1 = User(1, "Admin", "Admin", "admin@mail.ru", "ADMIN")
val user2 = User(2, "User1", "123", "u1@mail.ru", "USER") val user2 = User(2, "User1", "123", "u1@mail.ru", "USER")
@ -57,7 +60,7 @@ abstract class AppDatabase : RoomDatabase() {
bookDao.insert(book1) bookDao.insert(book1)
bookDao.insert(book2) bookDao.insert(book2)
bookDao.insert(book3) bookDao.insert(book3)
bookDao.insert(book4) bookDao.insert(book4) */
} }
} }

View File

@ -8,7 +8,7 @@ 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 = 0, val id: Int = 0,
@ColumnInfo(name = "author_name") @ColumnInfo(name = "author_name")
val name: String val name: String
) { ) {
@ -16,11 +16,11 @@ data class Author(
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Author other as Author
if (uid != other.uid) return false if (id != other.id) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return uid ?: -1 return id ?: -1
} }
} }

View File

@ -12,10 +12,10 @@ import androidx.room.Ignore
tableName = "books", foreignKeys = [ tableName = "books", foreignKeys = [
ForeignKey( ForeignKey(
entity = Author::class, entity = Author::class,
parentColumns = ["uid"], parentColumns = ["id"],
childColumns = ["author_id"], childColumns = ["author_id"],
onDelete = ForeignKey.RESTRICT, onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.RESTRICT onUpdate = ForeignKey.CASCADE
), ),
ForeignKey( ForeignKey(
entity = User::class, entity = User::class,
@ -28,7 +28,7 @@ import androidx.room.Ignore
) )
data class Book( data class Book(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int = 0, val id: Int = 0,
@ColumnInfo(name = "title") @ColumnInfo(name = "title")
val title: String, val title: String,
@ColumnInfo(name = "description") @ColumnInfo(name = "description")
@ -47,11 +47,11 @@ data class Book(
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Book other as Book
if (uid != other.uid) return false if (id != other.id) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return uid ?: -1 return id ?: -1
} }
} }

View File

@ -0,0 +1,26 @@
package com.example.myapplication.db.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
enum class RemoteKeyType(private val type: String) {
BOOK(Book::class.simpleName ?: "Book"),
AUTHOR(Author::class.simpleName ?: "Author");
@TypeConverter
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }
@TypeConverter
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
}
@Entity(tableName = "remote_keys")
data class RemoteKeys(
@PrimaryKey val entityId: Int,
@TypeConverters(RemoteKeyType::class)
val type: RemoteKeyType,
val prevKey: Int?,
val nextKey: Int?
)

View File

@ -9,7 +9,7 @@ interface AuthorRepository {
suspend fun getAllDrop(): List<Author> suspend fun getAllDrop(): List<Author>
suspend fun getByUid(uid: Int): Author suspend fun getByUid(uid: Int): Author
fun loadAllAuthorsPaged(): Flow<PagingData<Author>> fun loadAllAuthorsPaged(): Flow<PagingData<Author>>
suspend fun insert(author: Author) suspend fun insertAuthor(author: Author)
suspend fun update(author: Author) suspend fun updateAuthor(author: Author)
suspend fun delete(author: Author) suspend fun deleteAuthor(author: Author)
} }

View File

@ -10,10 +10,10 @@ interface BookRepository {
suspend fun getAll(): List<Book> suspend fun getAll(): List<Book>
suspend fun getByUid(uid: Int): Book suspend fun getByUid(uid: Int): Book
suspend fun getBySearch(searchStr: String): List<Book> suspend fun getBySearch(searchStr: String): List<Book>
fun getByUserId(userId: Int): Flow<List<Book>> suspend fun getByUserId(userId: Int): List<Book>
fun getByAuthorId(authorId: Int): Flow<List<Book>> suspend fun getByAuthorId(authorId: Int): List<Book>
fun loadAllBooksPaged(): Flow<PagingData<Book>> fun loadAllBooksPaged(): Flow<PagingData<Book>>
suspend fun insert(book: Book) suspend fun insertBook(book: Book)
suspend fun update(book: Book) suspend fun updateBook(book: Book)
suspend fun delete(book: Book) suspend fun deleteBook(book: Book)
} }

View File

@ -13,6 +13,8 @@ class OfflineAuthorRepository(private val authorDao: AuthorDao) : AuthorReposito
override suspend fun getAll(): List<Author> = authorDao.getAll() override suspend fun getAll(): List<Author> = authorDao.getAll()
override suspend fun getAllDrop(): List<Author> = authorDao.getAllDrop() override suspend fun getAllDrop(): List<Author> = authorDao.getAllDrop()
override suspend fun getByUid(uid: Int): Author = authorDao.getByUid(uid) override suspend fun getByUid(uid: Int): Author = authorDao.getByUid(uid)
suspend fun insertAuthors(authors: List<Author>) =
authorDao.insert(*authors.toTypedArray())
override fun loadAllAuthorsPaged(): Flow<PagingData<Author>> = Pager( override fun loadAllAuthorsPaged(): Flow<PagingData<Author>> = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = AppContainer.LIMIT, pageSize = AppContainer.LIMIT,
@ -21,9 +23,10 @@ class OfflineAuthorRepository(private val authorDao: AuthorDao) : AuthorReposito
pagingSourceFactory = authorDao::loadAllAuthorsPaged pagingSourceFactory = authorDao::loadAllAuthorsPaged
).flow ).flow
fun loadAuthorsPaged(): PagingSource<Int, Author> = authorDao.loadAllAuthorsPaged() fun loadAuthorsPaged(): PagingSource<Int, Author> = authorDao.loadAllAuthorsPaged()
override suspend fun insert(author: Author) = authorDao.insert(author) suspend fun clearAll() = authorDao.clearAll()
override suspend fun insertAuthor(author: Author) = authorDao.insert(author)
override suspend fun update(author: Author) = authorDao.update(author) override suspend fun updateAuthor(author: Author) = authorDao.update(author)
override suspend fun delete(author: Author) = authorDao.delete(author) override suspend fun deleteAuthor(author: Author) = authorDao.delete(author)
} }

View File

@ -7,7 +7,6 @@ import androidx.paging.PagingSource
import com.example.myapplication.db.database.AppContainer import com.example.myapplication.db.database.AppContainer
import com.example.myapplication.db.dao.BookDao import com.example.myapplication.db.dao.BookDao
import com.example.myapplication.db.model.Book import com.example.myapplication.db.model.Book
import com.example.myapplication.db.model.BookWithAuthor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class OfflineBookRepository(private val bookDao: BookDao) : BookRepository { class OfflineBookRepository(private val bookDao: BookDao) : BookRepository {
@ -17,8 +16,10 @@ class OfflineBookRepository(private val bookDao: BookDao) : BookRepository {
override suspend fun getBySearch(searchStr: String): List<Book> = bookDao.getBySearch(searchStr) override suspend fun getBySearch(searchStr: String): List<Book> = bookDao.getBySearch(searchStr)
override fun getByUserId(userId: Int): Flow<List<Book>> = bookDao.getByUserId(userId) override suspend fun getByUserId(userId: Int): List<Book> = bookDao.getByUserId(userId)
override fun getByAuthorId(authorId: Int): Flow<List<Book>> = bookDao.getByAuthorId(authorId) override suspend fun getByAuthorId(authorId: Int): List<Book> = bookDao.getByAuthorId(authorId)
suspend fun insertBooks(books: List<Book>) =
bookDao.insert(*books.toTypedArray())
override fun loadAllBooksPaged(): Flow<PagingData<Book>> = Pager( override fun loadAllBooksPaged(): Flow<PagingData<Book>> = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = AppContainer.LIMIT, pageSize = AppContainer.LIMIT,
@ -27,9 +28,10 @@ class OfflineBookRepository(private val bookDao: BookDao) : BookRepository {
pagingSourceFactory = bookDao::loadAllBooksPaged pagingSourceFactory = bookDao::loadAllBooksPaged
).flow ).flow
fun loadBooksPaged(): PagingSource<Int, Book> = bookDao.loadAllBooksPaged() fun loadBooksPaged(): PagingSource<Int, Book> = bookDao.loadAllBooksPaged()
override suspend fun insert(book: Book) = bookDao.insert(book) suspend fun clearAll() = bookDao.clearAll()
override suspend fun insertBook(book: Book) = bookDao.insert(book)
override suspend fun update(book: Book) = bookDao.update(book) override suspend fun updateBook(book: Book) = bookDao.update(book)
override suspend fun delete(book: Book) = bookDao.delete(book) override suspend fun deleteBook(book: Book) = bookDao.delete(book)
} }

View File

@ -0,0 +1,16 @@
package com.example.myapplication.db.respository
import com.example.myapplication.db.dao.RemoteKeysDao
import com.example.myapplication.db.model.RemoteKeyType
import com.example.myapplication.db.model.RemoteKeys
class OfflineRemoteKeyRepository( private val remoteKeysDao: RemoteKeysDao) : RemoteKeyRepository {
override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) =
remoteKeysDao.getRemoteKeys(id, type)
override suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys>) =
remoteKeysDao.insertAll(remoteKeys)
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
remoteKeysDao.clearRemoteKeys(type)
}

View File

@ -6,11 +6,11 @@ import com.example.myapplication.db.model.Book;
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository { class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAll(): Flow<List<User>> = userDao.getAll() override suspend fun getAll(): List<User> = userDao.getAll()
override suspend fun getByUid(uid: Int): User = userDao.getByUid(uid) override suspend fun getByUid(uid: Int): User? = userDao.getByUid(uid)
override fun getUserBooks(userid: Int): Flow<List<Book>> = userDao.getUserBooks(userid) override suspend fun getUserBooks(userid: Int): List<Book> = userDao.getUserBooks(userid)
override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password) override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password)

View File

@ -0,0 +1,10 @@
package com.example.myapplication.db.respository
import com.example.myapplication.db.model.RemoteKeyType
import com.example.myapplication.db.model.RemoteKeys
interface RemoteKeyRepository {
suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys?
suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys>)
suspend fun deleteRemoteKey(type: RemoteKeyType)
}

View File

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

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">100.87.48.148</domain>
<domain includeSubdomains="true">192.168.56.1</domain>
</domain-config>
</network-security-config>

View File

@ -3,4 +3,5 @@ plugins {
id("com.android.application") version "8.1.2" apply false id("com.android.application") version "8.1.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.20" apply false id("org.jetbrains.kotlin.android") version "1.8.20" apply false
id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.20" apply false
} }