lab 5 complete

This commit is contained in:
AnnZhimol 2023-12-13 14:49:20 +04:00
parent 5f4c36f00a
commit 9b7acce5b6
53 changed files with 1347 additions and 609 deletions

View File

@ -3,6 +3,7 @@ plugins {
id("com.android.application")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
@ -95,4 +96,12 @@ dependencies {
implementation ("androidx.paging:paging-guava:$paging_version")
// optional - Jetpack Compose integration
implementation ("androidx.paging:paging-compose:1.0.0-alpha18")
// retrofit
val retrofitVersion = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("androidx.paging:paging-compose:3.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@ -12,6 +12,8 @@
android:supportsRtl="true"
android:theme="@style/Theme.PMULabs"
android:name=".NewsPortalApplication"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"

View File

@ -0,0 +1,159 @@
package com.example.pmulabs.api
import com.example.pmulabs.api.model.ArticleRemote
import com.example.pmulabs.api.model.CommentRemote
import com.example.pmulabs.api.model.TagRemote
import com.example.pmulabs.api.model.UserRemote
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
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 NewsPortalService {
@GET("users")
suspend fun getUsers(): List<UserRemote>
@GET("tags")
suspend fun getTags(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<TagRemote>
@GET("tags")
suspend fun getAllTags(): List<TagRemote>
@GET("articles")
suspend fun getAllArticles(): List<ArticleRemote>
@GET("comments")
suspend fun getAllComments(): List<CommentRemote>
@GET("articles")
suspend fun getArticles(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<ArticleRemote>
@GET("comments")
suspend fun getComments(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<CommentRemote>
@GET("comments")
suspend fun getCountComment(
@Query("articleId") articleId: Int
): List<CommentRemote>
@GET("comments")
suspend fun getUserComments(@Query("userId") userId: Int): List<CommentRemote>
@GET("articles")
suspend fun getUserArticles(@Query("userId") userId: Int): List<ArticleRemote>
@GET("tags")
suspend fun getUserTags(@Query("userId") userId: Int): List<TagRemote>
@GET("users/{id}")
suspend fun getUser(
@Path("id") id: Int,
): 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
@GET("tags")
suspend fun getTagByName(
@Query("title") title: String,
): List<TagRemote>
@GET("tags/{id}")
suspend fun getTag(
@Path("id") id: Int,
): TagRemote
@POST("tags")
suspend fun createTag(
@Body tag: TagRemote,
): TagRemote
@PUT("tags/{id}")
suspend fun updateTag(
@Path("id") id: Int,
@Body tag: TagRemote,
): TagRemote
@DELETE("tags/{id}")
suspend fun deleteTag(
@Path("id") id: Int,
): TagRemote
@GET("articles/{id}")
suspend fun getArticle(
@Path("id") id: Int,
): ArticleRemote
@POST("articles")
suspend fun createArticle(
@Body article: ArticleRemote,
): ArticleRemote
@PUT("articles/{id}")
suspend fun updateArticle(
@Path("id") id: Int,
@Body article: ArticleRemote,
): ArticleRemote
@DELETE("articles/{id}")
suspend fun deleteArticle(
@Path("id") id: Int,
): ArticleRemote
@GET("comments/{id}")
suspend fun getComment(
@Path("id") id: Int,
): CommentRemote
@POST("comments")
suspend fun createComment(
@Body comment: CommentRemote,
): CommentRemote
@PUT("comments/{id}")
suspend fun updateComment(
@Path("id") id: Int,
@Body comment: CommentRemote,
): CommentRemote
@DELETE("comments/{id}")
suspend fun deleteComment(
@Path("id") id: Int,
): CommentRemote
companion object {
private const val BASE_URL = "http://10.0.2.2:8079/"
@Volatile
private var INSTANCE: NewsPortalService? = null
fun getInstance(): NewsPortalService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
.create(NewsPortalService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,112 @@
package com.example.pmulabs.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.model.toArticle
import com.example.pmulabs.api.repository.RestTagRepository
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
import com.example.pmulabs.room.repository.OfflineArticleRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class ArticleRemoteMediator(
private val service: NewsPortalService,
private val dbArticleRepository: OfflineArticleRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val tagRestRepository: RestTagRepository,
private val database: NewsPortalDatabase
) : RemoteMediator<Int, Article>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Article>
): 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 articles = service.getArticles(page, state.config.pageSize).map { it.toArticle() }
val endOfPaginationReached = articles.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ARTICLE)
dbArticleRepository.clearArticles()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = articles.map {
it.id?.let { it1 ->
RemoteKeys(
entityId = it1,
type = RemoteKeyType.ARTICLE,
prevKey = prevKey,
nextKey = nextKey
)
}
}
dbRemoteKeyRepository.createRemoteKeys(keys)
tagRestRepository.getAllTags()
dbArticleRepository.insertArticles(articles)
}
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, Article>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { art ->
art.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.ARTICLE) }
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Article>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { art ->
art.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.ARTICLE) }
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Article>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { artid ->
dbRemoteKeyRepository.getAllRemoteKeys(artid, RemoteKeyType.ARTICLE)
}
}
}
}

View File

@ -0,0 +1,111 @@
package com.example.pmulabs.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.model.toComment
import com.example.pmulabs.api.repository.RestArticleRepository
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
import com.example.pmulabs.room.repository.OfflineCommentRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class CommentRemoteMediator(
private val service: NewsPortalService,
private val dbCommentRepository: OfflineCommentRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val articleRestRepository: RestArticleRepository,
private val database: NewsPortalDatabase
) : RemoteMediator<Int, Comment>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Comment>
): 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 comms = service.getComments(page, state.config.pageSize).map { it.toComment() }
val endOfPaginationReached = comms.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.COMMENT)
dbCommentRepository.clearComments()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = comms.map {
it.id?.let { it1 ->
RemoteKeys(
entityId = it1,
type = RemoteKeyType.COMMENT,
prevKey = prevKey,
nextKey = nextKey
)
}
}
dbRemoteKeyRepository.createRemoteKeys(keys)
articleRestRepository.getAllArticles()
dbCommentRepository.insertComments(comms)
}
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, Comment>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { comm ->
comm.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.COMMENT) }
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Comment>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { comm ->
comm.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.COMMENT) }
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Comment>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { commid ->
dbRemoteKeyRepository.getAllRemoteKeys(commid, RemoteKeyType.COMMENT)
}
}
}
}

View File

@ -0,0 +1,109 @@
package com.example.pmulabs.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.model.toTag
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import com.example.pmulabs.room.repository.OfflineTagRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class TagRemoteMediator(
private val service: NewsPortalService,
private val dbTagRepository: OfflineTagRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: NewsPortalDatabase
) : RemoteMediator<Int, Tag>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Tag>
): 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 tags = service.getTags(page, state.config.pageSize).map { it.toTag() }
val endOfPaginationReached = tags.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.TAG)
dbTagRepository.clearTags()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = tags.map {
it.id?.let { it1 ->
RemoteKeys(
entityId = it1,
type = RemoteKeyType.TAG,
prevKey = prevKey,
nextKey = nextKey
)
}
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbTagRepository.insertTags(tags)
}
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, Tag>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { tag ->
tag.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.TAG) }
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Tag>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { tag ->
tag.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.TAG) }
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Tag>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { tagid ->
dbRemoteKeyRepository.getAllRemoteKeys(tagid, RemoteKeyType.TAG)
}
}
}
}

View File

@ -0,0 +1,32 @@
package com.example.pmulabs.api.model
import com.example.pmulabs.room.models.Article
import kotlinx.serialization.Serializable
@Serializable
data class ArticleRemote(
val id: Int?=0,
var title: String="",
var text: String="",
val publishDate: Long=0,
val userId: Int=0,
var tagId: Int=0
)
fun ArticleRemote.toArticle(): Article = Article(
id,
title,
text,
publishDate,
userId,
tagId
)
fun Article.toArticleRemote(): ArticleRemote = ArticleRemote(
id,
title,
text,
publishDate,
userId,
tagId
)

View File

@ -0,0 +1,26 @@
package com.example.pmulabs.api.model
import com.example.pmulabs.room.models.Comment
import kotlinx.serialization.Serializable
@Serializable
data class CommentRemote(
val id: Int?=0,
var text: String="",
val userId: Int=0,
val articleId: Int=0
)
fun CommentRemote.toComment(): Comment = Comment(
id,
text,
userId,
articleId
)
fun Comment.toCommentRemote(): CommentRemote = CommentRemote(
id,
text,
userId,
articleId
)

View File

@ -0,0 +1,23 @@
package com.example.pmulabs.api.model
import com.example.pmulabs.room.models.Tag
import kotlinx.serialization.Serializable
@Serializable
data class TagRemote(
var title: String="",
val id: Int?=0,
val userId: Int=0
)
fun TagRemote.toTag(): Tag = Tag(
title,
id,
userId
)
fun Tag.toTagRemote(): TagRemote = TagRemote(
title,
id,
userId
)

View File

@ -0,0 +1,29 @@
package com.example.pmulabs.api.model
import com.example.pmulabs.room.models.User
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
val id: Int?=0,
var nickname: String="",
var email: String="",
var password: String="",
val role: String=""
)
fun UserRemote.toUser(): User = User(
id,
nickname,
email,
password,
role
)
fun User.toUserRemote(): UserRemote = UserRemote(
id,
nickname,
email,
password,
role
)

View File

@ -0,0 +1,79 @@
package com.example.pmulabs.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.mediator.ArticleRemoteMediator
import com.example.pmulabs.api.model.toArticle
import com.example.pmulabs.api.model.toArticleRemote
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.repository.ArticleRepository
import com.example.pmulabs.room.repository.OfflineArticleRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestArticleRepository(
private val service: NewsPortalService,
private val dbArticleRepository: OfflineArticleRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val dbTagRepository: RestTagRepository,
private val database: NewsPortalDatabase
) : ArticleRepository {
override suspend fun insertArticle(article: Article) {
service.createArticle(article.toArticleRemote()).toArticle()
}
override suspend fun updateArticle(article: Article) {
article.id?.let { service.updateArticle(it, article.toArticleRemote()).toArticle() }
}
override suspend fun deleteArticle(article: Article) {
article.id?.let { service.deleteArticle(it).toArticle() }
}
override suspend fun getAllArticles(): List<Article> {
dbTagRepository.getAllTags()
val existArticles = dbArticleRepository.getAllArticles().associateBy { it.id }.toMutableMap()
service.getAllArticles()
.map { it.toArticle() }
.forEach { art ->
val existArt = existArticles[art.id]
if (existArt == null) {
dbArticleRepository.insertArticle(art)
} else if (existArt != art) {
dbArticleRepository.updateArticle(art)
}
existArticles[art.id] = art
}
return existArticles.map { it.value }.sortedBy { it.id }
}
override suspend fun getArticleById(idArticle: Int): Article? =
idArticle?.let { service.getArticle(it).toArticle() }
override fun getArticles(): Flow<PagingData<Article>> {
val pagingSourceFactory = { dbArticleRepository.getAllArticlesPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = ArticleRemoteMediator(
service,
dbArticleRepository,
dbRemoteKeyRepository,
dbTagRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}

View File

@ -0,0 +1,85 @@
package com.example.pmulabs.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.mediator.CommentRemoteMediator
import com.example.pmulabs.api.model.toComment
import com.example.pmulabs.api.model.toCommentRemote
import com.example.pmulabs.api.model.toUser
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.OfflineCommentRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestCommentRepository(
private val service: NewsPortalService,
private val dbCommentRepository: OfflineCommentRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val dbArticleRepository: RestArticleRepository,
private val database: NewsPortalDatabase
) : CommentRepository {
override suspend fun insertComment(comment: Comment) {
service.createComment(comment.toCommentRemote()).toComment()
}
override suspend fun updateComment(comment: Comment) {
comment.id?.let { service.updateComment(it, comment.toCommentRemote()).toComment() }
}
override suspend fun deleteComment(comment: Comment) {
comment.id?.let { service.deleteComment(it).toComment() }
}
override suspend fun getAllComments(): List<Comment> {
val existComms = dbCommentRepository.getAllComments().associateBy { it.id }.toMutableMap()
service.getAllComments()
.map { it.toComment() }
.forEach { comm ->
val existComm = existComms[comm.id]
if (existComm == null) {
dbCommentRepository.insertComment(comm)
} else if (existComm != comm) {
dbCommentRepository.updateComment(comm)
}
existComms[comm.id] = comm
}
return existComms.map { it.value }.sortedBy { it.id }
}
override suspend fun getCountComment(idArticle: Int?): Int? {
return idArticle?.let { service.getCountComment(it).size }
}
override suspend fun getUser(commId : Int) : User? =
commId?.let { service.getUser(it).toUser() }
override fun getComments(): Flow<PagingData<Comment>> {
val pagingSourceFactory = { dbCommentRepository.getAllCommentsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = CommentRemoteMediator(
service,
dbCommentRepository,
dbRemoteKeyRepository,
dbArticleRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}

View File

@ -0,0 +1,80 @@
package com.example.pmulabs.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.mediator.TagRemoteMediator
import com.example.pmulabs.api.model.toTag
import com.example.pmulabs.api.model.toTagRemote
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import com.example.pmulabs.room.repository.OfflineTagRepository
import com.example.pmulabs.room.repository.TagRepository
import kotlinx.coroutines.flow.Flow
class RestTagRepository(
private val service: NewsPortalService,
private val dbTagRepository: OfflineTagRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: NewsPortalDatabase
) : TagRepository {
override suspend fun insertTag(tag: Tag) {
service.createTag(tag.toTagRemote()).toTag()
}
override suspend fun updateTag(tag: Tag) {
tag.id?.let { service.updateTag(it, tag.toTagRemote()).toTag() }
}
override suspend fun deleteTag(tag: Tag) {
tag.id?.let { service.deleteTag(it).toTag() }
}
override suspend fun getAllTags(): List<Tag> {
val existTags = dbTagRepository.getAllTags().associateBy { it.id }.toMutableMap()
service.getAllTags()
.map { it.toTag() }
.forEach { tag ->
val existTag = existTags[tag.id]
if (existTag == null) {
dbTagRepository.insertTag(tag)
} else if (existTag != tag) {
dbTagRepository.updateTag(tag)
}
existTags[tag.id] = tag
}
return existTags.map { it.value }.sortedBy { it.id }
}
override suspend fun getTagById(idTag: Int): Tag? =
idTag?.let { service.getTag(it).toTag() }
override suspend fun getTagByName(nameTag: String): Tag? =
nameTag?.let { service.getTagByName(it)[0].toTag() }
override fun getTags(): Flow<PagingData<Tag>> {
val pagingSourceFactory = { dbTagRepository.getAllTagsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = TagRemoteMediator(
service,
dbTagRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}

View File

@ -0,0 +1,119 @@
package com.example.pmulabs.api.repository
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.model.toArticle
import com.example.pmulabs.api.model.toComment
import com.example.pmulabs.api.model.toTag
import com.example.pmulabs.api.model.toUser
import com.example.pmulabs.api.model.toUserRemote
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.OfflineArticleRepository
import com.example.pmulabs.room.repository.OfflineCommentRepository
import com.example.pmulabs.room.repository.OfflineTagRepository
import com.example.pmulabs.room.repository.OfflineUserRepository
import com.example.pmulabs.room.repository.UserRepository
class RestUserRepository(
private val service: NewsPortalService,
private val dbUserRepository: OfflineUserRepository,
private val dbCommentRepository: OfflineCommentRepository,
private val dbTagRepository: OfflineTagRepository,
private val dbArticleRepository: OfflineArticleRepository
) : UserRepository {
override suspend fun insertUser(user: User) {
service.createUser(user.toUserRemote()).toUser()
}
override suspend fun updateUser(user: User) {
user.id?.let { service.updateUser(it, user.toUserRemote()).toUser() }
}
override suspend fun deleteUser(user: User) {
user.id?.let { service.deleteUser(it).toUser() }
}
override suspend fun getAllUsers(): List<User> {
val existUsers = dbUserRepository.getAllUsers().associateBy { it.id }.toMutableMap()
service.getUsers()
.map { it.toUser() }
.forEach { user ->
val existUser = existUsers[user.id]
if (existUser == null) {
dbUserRepository.insertUser(user)
} else if (existUser != user) {
dbUserRepository.updateUser(user)
}
existUsers[user.id] = user
}
return existUsers.map { it.value }.sortedBy { it.id }
}
override suspend fun getUserById(idUser: Int?): User? =
idUser?.let { service.getUser(it).toUser() }
override suspend fun getUserComms(idUser: Int): List<Comment> {
val existComments = dbUserRepository.getUserComms(idUser).associateBy { it.id }.toMutableMap()
service.getUserComments(idUser)
.map { it.toComment() }
.forEach { comm ->
if(comm.userId==idUser) {
val existComm = existComments[comm.id]
if (existComm == null) {
dbCommentRepository.insertComment(comm)
} else if (existComm != comm) {
dbCommentRepository.updateComment(comm)
}
existComments[comm.id] = comm
}
}
return existComments.map { it.value }.sortedBy { it.id }
}
override suspend fun getUserArticles(idUser: Int): List<Article> {
val existArticles = dbUserRepository.getUserArticles(idUser).associateBy { it.id }.toMutableMap()
service.getUserArticles(idUser)
.map { it.toArticle() }
.forEach { art ->
if(art.userId==idUser) {
val existArt = existArticles[art.id]
if (existArt == null) {
dbArticleRepository.insertArticle(art)
} else if (existArt != art) {
dbArticleRepository.updateArticle(art)
}
existArticles[art.id] = art
}
}
return existArticles.map { it.value }.sortedBy { it.id }
}
override suspend fun getUserTags(idUser: Int): List<Tag> {
val existTags = dbUserRepository.getUserTags(idUser).associateBy { it.id }.toMutableMap()
service.getUserTags(idUser)
.map { it.toTag() }
.forEach { tag ->
if(tag.userId==idUser) {
val existTag = existTags[tag.id]
if (existTag == null) {
dbTagRepository.insertTag(tag)
} else if (existTag != tag) {
dbTagRepository.updateTag(tag)
}
existTags[tag.id] = tag
}
}
return existTags.map { it.value }.sortedBy { it.id }
}
}

View File

@ -68,6 +68,7 @@ import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticleItemViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
@ -180,7 +181,7 @@ fun ArticleItem(navController: NavController,article: Article, modifier: Modifie
text= {Text(text=label)},
onClick = { selectedText = label
coroutineScope.launch {
tagId= articleItemViewModel.getTagByName(selectedText).id!!
tagId= articleItemViewModel.getTagByName(selectedText)?.id!!
}
}
)
@ -242,15 +243,18 @@ fun ArticleItem(navController: NavController,article: Article, modifier: Modifie
),
onClick = {
openDialog = false
article.text=text
article.title=title
tagName=selectedText
Log.d("Tag",tagId.toString())
if(tagId!=null) {
article.text = text
article.title = title
tagName = selectedText
Log.d("Tag", tagId.toString())
if (tagId != null) {
article.tagId = tagId
}
coroutineScope.launch {
articleItemViewModel.updateArticle(article)
val upResult = async {
articleItemViewModel.updateArticle(article)
}
upResult.await()
}
},
modifier = Modifier

View File

@ -1,6 +1,7 @@
package com.example.pmulabs.designElem.items
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -51,20 +52,12 @@ import kotlinx.coroutines.launch
@SuppressLint("CoroutineCreationDuringComposition", "UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CommentItem(navController: NavController,modifier: Modifier = Modifier, comm : Comment,commentItemViewModel: CommentItemViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
fun CommentItem(navController: NavController, modifier: Modifier = Modifier, comm : Comment, user : User, commentItemViewModel: CommentItemViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
var openDialog by remember { mutableStateOf(false) }
var text by remember { mutableStateOf(comm.text) }
val coroutineScope = rememberCoroutineScope()
commentItemViewModel.getUsersByArticle(comm.articleId)
var users by mutableStateOf(commentItemViewModel.userListByArticle)
var user by mutableStateOf<User?>(null)
for(item in users){
if(comm.userId==item.id){
user=item
}
}
if (openDialog) {
AlertDialog(
@ -126,7 +119,7 @@ fun CommentItem(navController: NavController,modifier: Modifier = Modifier, comm
}
},
modifier = Modifier
.padding(start=100.dp)
.padding(start = 100.dp)
.fillMaxWidth(0.5f)
.height(40.dp)
) {
@ -150,28 +143,29 @@ fun CommentItem(navController: NavController,modifier: Modifier = Modifier, comm
.padding(start = 10.dp,top=10.dp)
) {
Row() {
Log.d("User",user.toString())
Text(
text = "${user?.nickname}",
color = Color(0xff423a99),
style = TextStyle(
textDecoration = TextDecoration.Underline,
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
text = "${user.nickname}",
color = Color(0xff423a99),
style = TextStyle(
textDecoration = TextDecoration.Underline,
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
)
if(getUser?.id==comm.userId) {
Icon(
modifier=Modifier
modifier= Modifier
.requiredHeight(20.dp)
.clickable {
openDialog=true
openDialog = true
},
imageVector = Icons.Filled.Edit,
contentDescription = "Update Icon",
tint = Color(0xff423a99)
)
Icon(
modifier=Modifier
modifier= Modifier
.requiredHeight(20.dp)
.clickable {
coroutineScope.launch {

View File

@ -1,38 +1,78 @@
package com.example.pmulabs.room
import android.content.Context
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.repository.RestArticleRepository
import com.example.pmulabs.api.repository.RestCommentRepository
import com.example.pmulabs.api.repository.RestTagRepository
import com.example.pmulabs.api.repository.RestUserRepository
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.repository.ArticleRepository
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.OfflineArticleRepository
import com.example.pmulabs.room.repository.OfflineCommentRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import com.example.pmulabs.room.repository.OfflineTagRepository
import com.example.pmulabs.room.repository.OfflineUserRepository
import com.example.pmulabs.room.repository.TagRepository
import com.example.pmulabs.room.repository.UserRepository
interface AppContainer {
val userRepository: UserRepository
val articleRepository: ArticleRepository
val tagRepository: TagRepository
val commentRepository: CommentRepository
val userRestRepository: RestUserRepository
val tagRestRepository: RestTagRepository
val articleRestRepository: RestArticleRepository
val commentRestRepository: RestCommentRepository
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 10
}
}
class AppDataContainer(private val context: Context) : AppContainer {
override val userRepository: UserRepository by lazy {
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(NewsPortalDatabase.getInstance(context).userDao())
}
override val articleRepository: ArticleRepository by lazy {
private val articleRepository: OfflineArticleRepository by lazy {
OfflineArticleRepository(NewsPortalDatabase.getInstance(context).articleDao())
}
override val tagRepository: TagRepository by lazy {
private val tagRepository: OfflineTagRepository by lazy {
OfflineTagRepository(NewsPortalDatabase.getInstance(context).tagDao())
}
override val commentRepository: CommentRepository by lazy {
private val commentRepository: OfflineCommentRepository by lazy {
OfflineCommentRepository(NewsPortalDatabase.getInstance(context).commentDao())
}
companion object {
const val TIMEOUT = 5000L
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(NewsPortalDatabase.getInstance(context).remoteKeysDao())
}
override val userRestRepository: RestUserRepository by lazy {
RestUserRepository(
NewsPortalService.getInstance(),
userRepository,
commentRepository,
tagRepository,
articleRepository
)
}
override val tagRestRepository: RestTagRepository by lazy {
RestTagRepository(
NewsPortalService.getInstance(),
tagRepository,
remoteKeyRepository,
NewsPortalDatabase.getInstance(context)
)
}
override val articleRestRepository: RestArticleRepository by lazy {
RestArticleRepository(
NewsPortalService.getInstance(),
articleRepository,
remoteKeyRepository,
tagRestRepository,
NewsPortalDatabase.getInstance(context)
)
}
override val commentRestRepository: RestCommentRepository by lazy {
RestCommentRepository(
NewsPortalService.getInstance(),
commentRepository,
remoteKeyRepository,
articleRestRepository,
NewsPortalDatabase.getInstance(context)
)
}
}

View File

@ -1,5 +1,6 @@
package com.example.pmulabs.room.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
@ -11,20 +12,23 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface ArticleDao {
@Query("select * from article")
fun getAll(): Flow<List<Article>>
fun getAll(): List<Article>
@Query("select * from article where article.id = :idArticle")
fun getArticleById(idArticle: Int): Flow<Article>
@Query("SELECT * FROM article ORDER BY id DESC LIMIT :limit OFFSET :offset")
suspend fun getArticles(limit: Int, offset: Int): List<Article>
@Query("SELECT * FROM article ORDER BY id ASC")
fun getArticles(): PagingSource<Int, Article>
@Insert
suspend fun insert(article: Article)
suspend fun insert(vararg article: Article)
@Update
suspend fun update(article: Article)
@Delete
suspend fun delete(article: Article)
@Query("DELETE FROM tag")
suspend fun deleteAll()
}

View File

@ -1,30 +1,38 @@
package com.example.pmulabs.room.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.flow.Flow
@Dao
interface CommentDao {
@Query("select * from comment")
fun getAll(): Flow<List<Comment>>
fun getAll(): List<Comment>
@Query("select COUNT(*) from comment WHERE comment.text!='' AND comment.article_id= :idArticle")
fun getCountComment(idArticle : Int?) : Int
@Query("SELECT * FROM comment ORDER BY id DESC LIMIT :limit OFFSET :offset")
suspend fun getComments(limit: Int, offset: Int): List<Comment>
@Query("SELECT * FROM comment ORDER BY id ASC")
fun getComments(): PagingSource<Int, Comment>
@Query("SELECT * FROM user WHERE user.id=:commId")
fun getUser(commId:Int): Flow<User>
@Insert
suspend fun insert(comment: Comment)
suspend fun insert(vararg comment: Comment)
@Update
suspend fun update(comment: Comment)
@Delete
suspend fun delete(comment: Comment)
@Query("DELETE FROM tag")
suspend fun deleteAll()
}

View File

@ -0,0 +1,20 @@
package com.example.pmulabs.room.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.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

@ -1,5 +1,6 @@
package com.example.pmulabs.room.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
@ -11,7 +12,7 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface TagDao {
@Query("select * from tag")
fun getAll(): Flow<List<Tag>>
fun getAll(): List<Tag>
@Query("select * from tag where tag.id = :idTag")
fun getTagById(idTag: Int): Flow<Tag>
@ -19,15 +20,18 @@ interface TagDao {
@Query("select * from tag where tag.title = :nameTag")
fun getTagByName(nameTag: String): Flow<Tag>
@Query("SELECT * FROM tag ORDER BY id ASC LIMIT :limit OFFSET :offset")
suspend fun getTags(limit: Int, offset: Int): List<Tag>
@Query("SELECT * FROM tag ORDER BY id ASC")
fun getTags(): PagingSource<Int, Tag>
@Insert
suspend fun insert(tag: Tag)
suspend fun insert(vararg tag: Tag)
@Update
suspend fun update(tag: Tag)
@Delete
suspend fun delete(tag: Tag)
@Query("DELETE FROM tag")
suspend fun deleteAll()
}

View File

@ -14,19 +14,19 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("select * from user")
fun getAll(): Flow<List<User>>
suspend fun getAll(): List<User>
@Query("select * from user where user.id = :idUser")
fun getUserById(idUser: Int?): Flow<User>
@Query("select * from comment WHERE comment.text!='' AND comment.user_id= :idUser")
fun getUserComms(idUser: Int): Flow<List<Comment>>
suspend fun getUserComms(idUser: Int): List<Comment>
@Query("select * from article WHERE article.text!='' AND article.user_id= :idUser")
fun getUserArticles(idUser: Int): Flow<List<Article>>
suspend fun getUserArticles(idUser: Int): List<Article>
@Query("select * from tag WHERE tag.title!='' AND tag.user_id= :idUser")
fun getUserTags(idUser: Int): Flow<List<Tag>>
suspend fun getUserTags(idUser: Int): List<Tag>
@Insert
suspend fun insert(user: User)

View File

@ -4,30 +4,29 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.pmulabs.room.dao.ArticleDao
import com.example.pmulabs.room.dao.CommentDao
import com.example.pmulabs.room.dao.RemoteKeysDao
import com.example.pmulabs.room.dao.TagDao
import com.example.pmulabs.room.dao.UserDao
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.RemoteKeys
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Date
@Database(entities = [User::class, Tag::class, Comment::class, Article::class], version = 5, exportSchema = false)
@Database(entities = [RemoteKeys::class, User::class, Tag::class, Comment::class, Article::class], version = 5, exportSchema = false)
abstract class NewsPortalDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun tagDao(): TagDao
abstract fun commentDao(): CommentDao
abstract fun articleDao(): ArticleDao
abstract fun remoteKeysDao(): RemoteKeysDao
companion object {
private const val DB_NAME: String = "news-portal"
private const val DB_NAME: String = "newsportaldb"
@Volatile
private var INSTANCE: NewsPortalDatabase? = null
@ -41,12 +40,12 @@ abstract class NewsPortalDatabase : RoomDatabase() {
userDao.insert(user2)
userDao.insert(user3)
val tagDao = database.tagDao()
val tag1 = Tag(1, "Тег_1",2)
val tag2 = Tag(2, "Тег_2",1)
val tag3 = Tag(3, "Тег_3",3)
val tag4 = Tag(4, "Тег_4",2)
val tag5 = Tag(5, "Тег_5",1)
val tag6 = Tag(6, "Тег_6",3)
val tag1 = Tag(id=1, title = "Тег_1", userId = 2)
val tag2 = Tag(id=2, title ="Тег_2",userId = 1)
val tag3 = Tag(id=3, title ="Тег_3",userId = 3)
val tag4 = Tag(id=4, title ="Тег_4",userId = 2)
val tag5 = Tag(id=5, title ="Тег_5",userId = 1)
val tag6 = Tag(id=6, title ="Тег_6",userId = 3)
tagDao.insert(tag1)
tagDao.insert(tag2)
tagDao.insert(tag3)
@ -103,14 +102,14 @@ abstract class NewsPortalDatabase : RoomDatabase() {
NewsPortalDatabase::class.java,
DB_NAME
)
.addCallback(object : Callback() {
/*.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
populateDatabase()
}
}
})
})*/
.build()
.also { INSTANCE = it }
}

View File

@ -0,0 +1,27 @@
package com.example.pmulabs.room.models
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
enum class RemoteKeyType(private val type: String) {
ARTICLE(Article::class.simpleName ?: "Article"),
TAG(Tag::class.simpleName ?: "Tag"),
COMMENT(Comment::class.simpleName ?: "Comment");
@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

@ -10,10 +10,10 @@ import androidx.room.PrimaryKey
foreignKeys = [ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["user_id"], onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE)],
indices = [Index(value = ["title"], unique = true)])
data class Tag(
@PrimaryKey(autoGenerate = true)
val id: Int?,
@ColumnInfo(name = "title")
var title: String,
@PrimaryKey(autoGenerate = true)
val id: Int?,
@ColumnInfo(name = "user_id")
val userId: Int
) {

View File

@ -1,38 +0,0 @@
package com.example.pmulabs.room.pagingSource
import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.pmulabs.room.dao.ArticleDao
import com.example.pmulabs.room.models.Article
import kotlinx.coroutines.delay
class ArticlePagingSource(
private val dao: ArticleDao,
) : PagingSource<Int, Article>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Article> {
val page = params.key ?: 0
return try {
Log.d("MainPagingSource", "load: $page")
val entities = dao.getArticles(params.loadSize, page * params.loadSize)
if (page != 0) delay(1000)
LoadResult.Page(
data = entities,
prevKey = if (page == 0) null else page - 1,
nextKey = if (entities.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override val jumpingSupported: Boolean = true
override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}

View File

@ -1,38 +0,0 @@
package com.example.pmulabs.room.pagingSource
import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.pmulabs.room.dao.CommentDao
import com.example.pmulabs.room.models.Comment
import kotlinx.coroutines.delay
class CommentPagingSource(
private val dao: CommentDao,
) : PagingSource<Int, Comment>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Comment> {
val page = params.key ?: 0
return try {
Log.d("MainPagingSource", "load: $page")
val entities = dao.getComments(params.loadSize, page * params.loadSize)
if (page != 0) delay(1000)
LoadResult.Page(
data = entities,
prevKey = if (page == 0) null else page - 1,
nextKey = if (entities.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override val jumpingSupported: Boolean = true
override fun getRefreshKey(state: PagingState<Int, Comment>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}

View File

@ -1,38 +0,0 @@
package com.example.pmulabs.room.pagingSource
import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.pmulabs.room.dao.TagDao
import com.example.pmulabs.room.models.Tag
import kotlinx.coroutines.delay
class TagPagingSource(
private val dao: TagDao,
) : PagingSource<Int, Tag>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Tag> {
val page = params.key ?: 0
return try {
Log.d("MainPagingSource", "load: $page")
val entities = dao.getTags(params.loadSize, page * params.loadSize)
if (page != 0) delay(1000)
LoadResult.Page(
data = entities,
prevKey = if (page == 0) null else page - 1,
nextKey = if (entities.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override val jumpingSupported: Boolean = true
override fun getRefreshKey(state: PagingState<Int, Tag>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}

View File

@ -8,7 +8,7 @@ interface ArticleRepository {
suspend fun insertArticle(article: Article)
suspend fun updateArticle(article: Article)
suspend fun deleteArticle(article: Article)
fun getAllArticles(): Flow<List<Article>>
fun getArticleById(idArticle: Int): Flow<Article>
suspend fun getAllArticles(): List<Article>
suspend fun getArticleById(idArticle: Int): Article?
fun getArticles(): Flow<PagingData<Article>>
}

View File

@ -2,13 +2,15 @@ package com.example.pmulabs.room.repository
import androidx.paging.PagingData
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.flow.Flow
interface CommentRepository {
suspend fun insertComment(comment: Comment)
suspend fun updateComment(comment: Comment)
suspend fun deleteComment(comment: Comment)
fun getAllComments(): Flow<List<Comment>>
fun getCountComment(idArticle : Int?) : Int
suspend fun getAllComments(): List<Comment>
suspend fun getCountComment(idArticle : Int?) : Int?
fun getComments(): Flow<PagingData<Comment>>
suspend fun getUser(commId: Int): User?
}

View File

@ -3,17 +3,29 @@ package com.example.pmulabs.room.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.pmulabs.room.dao.ArticleDao
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.pagingSource.ArticlePagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class OfflineArticleRepository(private val articleDao: ArticleDao) : ArticleRepository {
override suspend fun insertArticle(article: Article) = articleDao.insert(article)
override suspend fun updateArticle(article: Article) = articleDao.update(article)
override suspend fun deleteArticle(article: Article) = articleDao.delete(article)
override fun getAllArticles(): Flow<List<Article>> = articleDao.getAll()
override fun getArticleById(idArticle: Int): Flow<Article> = articleDao.getArticleById(idArticle)
override fun getArticles(): Flow<PagingData<Article>> = Pager(config = PagingConfig(pageSize = 4, jumpThreshold = 4, initialLoadSize = 4) ){ ArticlePagingSource(articleDao) }.flow
override suspend fun getAllArticles(): List<Article> = articleDao.getAll()
override suspend fun getArticleById(idArticle: Int): Article? = articleDao.getArticleById(idArticle).first()
override fun getArticles(): Flow<PagingData<Article>> = Pager(
config = PagingConfig(
pageSize = 4,
enablePlaceholders = false
),
pagingSourceFactory = articleDao::getArticles
).flow
fun getAllArticlesPagingSource(): PagingSource<Int, Article> = articleDao.getArticles()
suspend fun clearArticles() = articleDao.deleteAll()
suspend fun insertArticles(articles: List<Article>) =
articleDao.insert(*articles.toTypedArray())
}

View File

@ -3,17 +3,34 @@ package com.example.pmulabs.room.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.pmulabs.room.dao.CommentDao
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.pagingSource.CommentPagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class OfflineCommentRepository(private val commentDao: CommentDao) : CommentRepository {
override suspend fun insertComment(comment: Comment) = commentDao.insert(comment)
override suspend fun updateComment(comment: Comment) = commentDao.update(comment)
override suspend fun deleteComment(comment: Comment) = commentDao.delete(comment)
override fun getAllComments(): Flow<List<Comment>> = commentDao.getAll()
override fun getCountComment(idArticle : Int?) : Int = commentDao.getCountComment(idArticle)
override fun getComments(): Flow<PagingData<Comment>> = Pager(config = PagingConfig(pageSize = 4, jumpThreshold = 4, initialLoadSize = 4) ){ CommentPagingSource(commentDao) }.flow
override suspend fun getAllComments(): List<Comment> = commentDao.getAll()
override suspend fun getCountComment(idArticle : Int?) : Int = commentDao.getCountComment(idArticle)
override fun getComments(): Flow<PagingData<Comment>> = Pager(
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false
),
pagingSourceFactory =commentDao::getComments
).flow
fun getAllCommentsPagingSource(): PagingSource<Int, Comment> = commentDao.getComments()
suspend fun clearComments() = commentDao.deleteAll()
override suspend fun getUser(commId: Int) = commentDao.getUser(commId).first()
suspend fun insertComments(comments: List<Comment>) {
commentDao.insert(*comments.toTypedArray())
}
}
}

View File

@ -0,0 +1,16 @@
package com.example.pmulabs.room.repository
import com.example.pmulabs.room.dao.RemoteKeysDao
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.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

@ -3,18 +3,32 @@ package com.example.pmulabs.room.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.dao.TagDao
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.pagingSource.TagPagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class OfflineTagRepository(private val tagDao: TagDao) : TagRepository {
override suspend fun insertTag(tag: Tag) = tagDao.insert(tag)
override suspend fun updateTag(tag: Tag) = tagDao.update(tag)
override suspend fun deleteTag(tag: Tag) = tagDao.delete(tag)
override fun getAllTags(): Flow<List<Tag>> = tagDao.getAll()
override fun getTagById(idTag: Int): Flow<Tag> = tagDao.getTagById(idTag)
override fun getTagByName(nameTag: String): Flow<Tag> = tagDao.getTagByName(nameTag)
override fun getTags(): Flow<PagingData<Tag>> = Pager(config = PagingConfig(pageSize = 4, jumpThreshold = 4, initialLoadSize = 4) ){ TagPagingSource(tagDao) }.flow
override suspend fun getAllTags(): List<Tag> = tagDao.getAll()
override suspend fun getTagById(idTag: Int): Tag? = tagDao.getTagById(idTag).first()
override suspend fun getTagByName(nameTag: String): Tag? = tagDao.getTagByName(nameTag).first()
override fun getTags(): Flow<PagingData<Tag>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = tagDao::getTags
).flow
fun getAllTagsPagingSource(): PagingSource<Int, Tag> = tagDao.getTags()
suspend fun clearTags() = tagDao.deleteAll()
suspend fun insertTags(tags: List<Tag>) =
tagDao.insert(*tags.toTypedArray())
}

View File

@ -5,15 +5,15 @@ import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override suspend fun insertUser(user: User) = userDao.insert(user)
override suspend fun updateUser(user: User) = userDao.update(user)
override suspend fun deleteUser(user: User) = userDao.delete(user)
override fun getAllUsers(): Flow<List<User>> = userDao.getAll()
override fun getUserById(idUser: Int?): Flow<User> = userDao.getUserById(idUser)
override fun getUserComms(idUser: Int): Flow<List<Comment>> = userDao.getUserComms(idUser)
override fun getUserArticles(idUser: Int): Flow<List<Article>> = userDao.getUserArticles(idUser)
override fun getUserTags(idUser: Int): Flow<List<Tag>> = userDao.getUserTags(idUser)
override suspend fun getAllUsers(): List<User> = userDao.getAll()
override suspend fun getUserById(idUser: Int?): User? = userDao.getUserById(idUser).first()
override suspend fun getUserComms(idUser: Int): List<Comment> = userDao.getUserComms(idUser)
override suspend fun getUserArticles(idUser: Int): List<Article> = userDao.getUserArticles(idUser)
override suspend fun getUserTags(idUser: Int): List<Tag> = userDao.getUserTags(idUser)
}

View File

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

View File

@ -8,8 +8,8 @@ interface TagRepository {
suspend fun insertTag(tag: Tag)
suspend fun updateTag(tag: Tag)
suspend fun deleteTag(tag: Tag)
fun getAllTags(): Flow<List<Tag>>
fun getTagById(idTag: Int): Flow<Tag>
fun getTagByName(nameTag: String): Flow<Tag>
suspend fun getAllTags(): List<Tag>
suspend fun getTagById(idTag: Int): Tag?
suspend fun getTagByName(nameTag: String): Tag?
fun getTags(): Flow<PagingData<Tag>>
}

View File

@ -4,15 +4,14 @@ import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.flow.Flow
interface UserRepository {
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)
fun getAllUsers(): Flow<List<User>>
fun getUserById(idUser: Int?): Flow<User>
fun getUserComms(idUser: Int): Flow<List<Comment>>
fun getUserArticles(idUser: Int): Flow<List<Article>>
fun getUserTags(idUser: Int): Flow<List<Tag>>
suspend fun getAllUsers(): List<User>
suspend fun getUserById(idUser: Int?): User?
suspend fun getUserComms(idUser: Int): List<Comment>
suspend fun getUserArticles(idUser: Int): List<Article>
suspend fun getUserTags(idUser: Int): List<Tag>
}

View File

@ -65,7 +65,7 @@ import java.util.Date
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ArticlePageScreen(navController: NavController, modifier: Modifier = Modifier,articlePageScreenViewModel: ArticlePageScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
articlePageScreenViewModel.setUserList()
var id =
navController.currentBackStackEntry?.arguments?.getString(ARTICLE_ARGUMENT_KEY).toString()
try {
@ -89,6 +89,8 @@ fun ArticlePageScreen(navController: NavController, modifier: Modifier = Modifie
val publishDate = formatter.format(Date(article?.publishDate ?: 0))
val comms = articlePageScreenViewModel.comments.collectAsLazyPagingItems()
var users = mutableStateOf( articlePageScreenViewModel.userList)
Log.d("ListOfUsers", users.toString())
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
val coroutineScope = rememberCoroutineScope()
@ -257,6 +259,7 @@ fun ArticlePageScreen(navController: NavController, modifier: Modifier = Modifie
items(count = comms.itemCount) { index ->
val comm = comms[index]
val user = comm?.userId?.let { users.value.get(it-1) }!!
if (comm != null) {
if (comm.articleId == article?.id && comm.text != "") {
Spacer(modifier = Modifier.padding(0.dp))
@ -265,7 +268,7 @@ fun ArticlePageScreen(navController: NavController, modifier: Modifier = Modifie
modifier = Modifier
.border(BorderStroke(3.dp, Color(0xff423a99))), color = Color(0xff423a99)
)
CommentItem(navController,comm = comm, currentUserViewModel = currentUserViewModel)
CommentItem(navController,comm = comm,user=user, currentUserViewModel = currentUserViewModel)
}
}
}

View File

@ -65,7 +65,9 @@ fun MainScreen(navController: NavController, modifier: Modifier = Modifier, arti
items(count = articles.itemCount) { index ->
val article = articles[index]
if (article != null) {
ArticleItem(navController=navController,article = article, currentUserViewModel = currentUserViewModel, onArticleClick = {navController.navigate(BottomBarScreen.ArticlePage.passId(article.id.toString()))})
ArticleItem(navController=navController,article = article, currentUserViewModel = currentUserViewModel, onArticleClick = {
navController.navigate(BottomBarScreen.ArticlePage.passId(article.id.toString()))
})
}
}
articles.apply {

View File

@ -30,7 +30,6 @@ import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
@ -52,7 +51,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
@ -63,10 +61,7 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.toSize
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.pmulabs.R
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.designElem.elem.ValidateEmail
import com.example.pmulabs.designElem.elem.isValidEmail
import com.example.pmulabs.room.models.Article
@ -83,15 +78,14 @@ import java.util.Date
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,articleScreenViewModel: ArticleScreenViewModel= viewModel(factory = AppViewModelProvider.Factory),tagItemViewModel: TagItemViewModel= viewModel(factory = AppViewModelProvider.Factory),articlePageScreenViewModel: ArticlePageScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val context = LocalContext.current
articleScreenViewModel.setArticleList()
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
var openDialogUser by remember { mutableStateOf(false) }
var email by remember { mutableStateOf(getUser?.email) }
var password by remember { mutableStateOf(getUser?.password) }
var nickname by remember { mutableStateOf(getUser?.nickname) }
val getArticles = articleScreenViewModel.articles.collectAsLazyPagingItems()
val getComms = articlePageScreenViewModel.comments.collectAsLazyPagingItems()
val getTags = tagItemViewModel.tags.collectAsLazyPagingItems()
val getArticles = articleScreenViewModel.getArticles
val coroutineScope = rememberCoroutineScope()
articlePageScreenViewModel.setTagList()
@ -178,7 +172,7 @@ fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,ar
selectedText = label
coroutineScope.launch {
tagId =
articlePageScreenViewModel.getTagByName(selectedText).id!!
articlePageScreenViewModel.getTagByName(selectedText)?.id!!
}
}
)
@ -523,159 +517,7 @@ fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,ar
}
}
}
item {
Box(
modifier = Modifier
.offset(
x = 9.dp,
y = 220.dp
)
.requiredWidth(width = 393.dp)
.requiredHeight(height = 182.dp)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 27.1240234375.dp
)
.requiredWidth(width = 393.dp)
.requiredHeight(height = 155.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 155.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(
border = BorderStroke(3.dp, Color(0xffdbdbf1)),
shape = RoundedCornerShape(5.dp)
)
)
LazyColumn(
contentPadding = PaddingValues(
top = 28.dp,
bottom = 0.dp,
start = 10.dp,
end = 10.dp
),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
if (getComms.itemCount != 0) {
items(count = getComms.itemCount) { index ->
val comment = getComms[index]
if (comment?.userId == getUser?.id) {
Spacer(modifier = Modifier.padding(5.dp))
Text(
text = "${comment?.text}",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.clickable {
navController.navigate(
BottomBarScreen.ArticlePage.passId(
comment?.articleId.toString()
)
)
}
)
Spacer(modifier = Modifier.padding(5.dp))
HorizontalDivider(
thickness = 3.dp,
modifier = Modifier
.border(BorderStroke(3.dp, Color(0xffdbdbf1))),
color = Color(0xffdbdbf1)
)
}
}
getComms.apply {
when {
loadState.refresh is LoadState.Loading -> {
item {
CircularProgressIndicator(
modifier = Modifier.fillParentMaxSize(),
color = Color(0xff423a99)
)
}
}
loadState.append is LoadState.Loading -> {
item {
CircularProgressIndicator(
modifier = Modifier.fillParentMaxSize(),
color = Color(0xff423a99)
)
}
}
loadState.refresh is LoadState.Error -> {
val err = getComms.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = getComms.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
} else {
item {
Text(
text = "Комментариев пока нет!",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.align(alignment = Alignment.TopStart)
)
}
}
}
}
Box(
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 54.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 54.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99))
)
Text(
text = "Comments:",
color = Color.White,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 24.dp,
y = 0.dp
)
.requiredWidth(width = 210.dp)
.requiredHeight(height = 54.dp)
.wrapContentHeight(align = Alignment.CenterVertically)
)
}
}
}
item {
Spacer(modifier = Modifier.padding(10.dp))
Box(
@ -685,7 +527,7 @@ fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,ar
y = 220.dp
)
.requiredWidth(width = 393.dp)
.requiredHeight(height = 182.dp)
.requiredHeight(height = 282.dp)
) {
Box(
modifier = Modifier
@ -695,12 +537,12 @@ fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,ar
y = 27.1240234375.dp
)
.requiredWidth(width = 393.dp)
.requiredHeight(height = 155.dp)
.requiredHeight(height = 255.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 155.dp)
.requiredHeight(height = 255.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(
@ -718,8 +560,8 @@ fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,ar
),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
if (getArticles.itemCount != 0) {
items(count = getArticles.itemCount) { index ->
if (getArticles.size != 0) {
items(count = getArticles.size) { index ->
val article = getArticles[index]
if (article?.userId == getUser?.id) {
Spacer(modifier = Modifier.padding(5.dp))
@ -732,13 +574,13 @@ fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,ar
),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.clickable {
/*.clickable {
navController.navigate(
BottomBarScreen.ArticlePage.passId(
article?.id.toString()
)
)
}
}*/
)
Spacer(modifier = Modifier.padding(5.dp))
HorizontalDivider(
@ -749,37 +591,6 @@ fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,ar
)
}
}
getArticles.apply {
when {
loadState.refresh is LoadState.Loading -> {
item {
CircularProgressIndicator(
modifier = Modifier.fillParentMaxSize(),
color = Color(0xff423a99)
)
}
}
loadState.append is LoadState.Loading -> {
item {
CircularProgressIndicator(
modifier = Modifier.fillParentMaxSize(),
color = Color(0xff423a99)
)
}
}
loadState.refresh is LoadState.Error -> {
val err = getArticles.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = getArticles.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
} else {
item {
Text(
@ -830,129 +641,6 @@ fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,ar
}
}
}
item {
Spacer(modifier = Modifier.padding(10.dp))
Box(
modifier = Modifier
.offset(
x = 9.dp,
y = 220.dp
)
.requiredWidth(width = 393.dp)
.requiredHeight(height = 182.dp)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 27.1240234375.dp
)
.requiredWidth(width = 393.dp)
.requiredHeight(height = 155.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 155.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(
border = BorderStroke(3.dp, Color(0xffdbdbf1)),
shape = RoundedCornerShape(5.dp)
)
)
LazyColumn(
contentPadding = PaddingValues(
top = 28.dp,
bottom = 0.dp,
start = 10.dp,
end = 10.dp
),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
if (getTags.itemCount != 0) {
items(count = getTags.itemCount) { index ->
val tag = getTags[index]
if (tag?.userId == getUser?.id) {
Spacer(modifier = Modifier.padding(5.dp))
Text(
text = "${tag?.title}",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.clickable {
navController.navigate(
BottomBarScreen.SearchByTag.passId(
tag?.id.toString()
)
)
}
)
Spacer(modifier = Modifier.padding(5.dp))
HorizontalDivider(
thickness = 3.dp,
modifier = Modifier
.border(BorderStroke(3.dp, Color(0xffdbdbf1))),
color = Color(0xffdbdbf1)
)
}
}
} else {
item {
Text(
text = "Тэгов пока нет!",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.align(alignment = Alignment.TopStart)
)
}
}
}
}
Box(
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 54.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 54.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99))
)
Text(
text = "Tags:",
color = Color.White,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 24.dp,
y = 0.dp
)
.requiredWidth(width = 210.dp)
.requiredHeight(height = 54.dp)
.wrapContentHeight(align = Alignment.CenterVertically)
)
}
}
}
}
}

View File

@ -117,7 +117,7 @@ fun TagsScreen(navController: NavController, modifier: Modifier = Modifier, tagI
onClick = {
openDialog = false
if(text.isNotEmpty()) {
val newTag = Tag(null, text, getUser?.id.toString().toInt())
val newTag = Tag(text, null, getUser?.id.toString().toInt())
coroutineScope.launch {
tagItemViewModel.insertTag(newTag)
}

View File

@ -9,28 +9,28 @@ import com.example.pmulabs.NewsPortalApplication
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
CurrentUserViewModel(newsPortalApplication().container.userRepository)
CurrentUserViewModel(newsPortalApplication().container.userRestRepository)
}
initializer {
TagItemViewModel(newsPortalApplication().container.tagRepository)
TagItemViewModel(newsPortalApplication().container.tagRestRepository)
}
initializer {
CommentItemViewModel(newsPortalApplication().container.userRepository,newsPortalApplication().container.commentRepository)
CommentItemViewModel(newsPortalApplication().container.userRestRepository,newsPortalApplication().container.commentRestRepository)
}
initializer {
ArticleItemViewModel(newsPortalApplication().container.tagRepository,newsPortalApplication().container.commentRepository,newsPortalApplication().container.articleRepository)
ArticleItemViewModel(newsPortalApplication().container.tagRestRepository,newsPortalApplication().container.commentRestRepository,newsPortalApplication().container.articleRestRepository)
}
initializer {
EntryScreenViewModel(newsPortalApplication().container.userRepository)
EntryScreenViewModel(newsPortalApplication().container.userRestRepository)
}
initializer {
RegisterScreenViewModel(newsPortalApplication().container.userRepository)
RegisterScreenViewModel(newsPortalApplication().container.userRestRepository)
}
initializer {
ArticleScreenViewModel(newsPortalApplication().container.articleRepository)
ArticleScreenViewModel(newsPortalApplication().container.articleRestRepository)
}
initializer {
ArticlePageScreenViewModel(newsPortalApplication().container.tagRepository,newsPortalApplication().container.commentRepository,newsPortalApplication().container.articleRepository,newsPortalApplication().container.userRepository)
ArticlePageScreenViewModel(newsPortalApplication().container.tagRestRepository,newsPortalApplication().container.commentRestRepository,newsPortalApplication().container.articleRestRepository,newsPortalApplication().container.userRestRepository)
}
}
}

View File

@ -11,7 +11,6 @@ import com.example.pmulabs.room.repository.ArticleRepository
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.TagRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -22,8 +21,8 @@ class ArticleItemViewModel(
) : ViewModel() {
var tagList by mutableStateOf<List<Tag>>(emptyList())
fun setTagList() {
viewModelScope.launch {
tagList=tagRepository.getAllTags().first()
viewModelScope.launch(Dispatchers.IO) {
tagList=tagRepository.getAllTags()
}
}
@ -35,11 +34,11 @@ class ArticleItemViewModel(
articleRepository.deleteArticle(article)
}
suspend fun getTagByName(name: String) : Tag {
return tagRepository.getTagByName(name).first()
suspend fun getTagByName(name: String) : Tag? {
return tagRepository.getTagByName(name)
}
suspend fun getCount(articleId: Int?): Int = withContext(Dispatchers.IO) {
suspend fun getCount(articleId: Int?): Int? = withContext(Dispatchers.IO) {
commentRepository.getCountComment(articleId)
}

View File

@ -14,9 +14,8 @@ import com.example.pmulabs.room.repository.ArticleRepository
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.TagRepository
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class ArticlePageScreenViewModel(
@ -31,19 +30,31 @@ class ArticlePageScreenViewModel(
var tag by mutableStateOf<Tag?>(null)
val comments: Flow<PagingData<Comment>> = commentRepository.getComments()
var getComments by mutableStateOf<List<Comment>>(emptyList())
fun setCommentList() {
viewModelScope.launch(Dispatchers.IO) {
getComments=commentRepository.getAllComments()
}
}
fun getArticleById(articleId:Int) {
articleid.value = articleId
viewModelScope.launch {
article = articleRepository.getArticleById(articleid.value!!)
.filterNotNull()
.first()
}
}
var userList by mutableStateOf<List<User>>(emptyList())
fun setUserList() {
viewModelScope.launch(Dispatchers.IO) {
userList=userRepository.getAllUsers()
}
}
var tagList by mutableStateOf<List<Tag>>(emptyList())
fun setTagList() {
viewModelScope.launch {
tagList=tagRepository.getAllTags().first()
viewModelScope.launch(Dispatchers.IO) {
tagList=tagRepository.getAllTags()
}
}
@ -59,16 +70,14 @@ class ArticlePageScreenViewModel(
articleRepository.insertArticle(article)
}
suspend fun getTagByName(name: String) : Tag {
return tagRepository.getTagByName(name).first()
suspend fun getTagByName(name: String) : Tag? {
return tagRepository.getTagByName(name)
}
fun getTagById(tagId:Int) {
tagid.value = tagId
viewModelScope.launch {
tag = tagRepository.getTagById(tagid.value!!)
.filterNotNull()
.first()
}
}
}

View File

@ -1,11 +1,24 @@
package com.example.pmulabs.viewModels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.repository.ArticleRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class ArticleScreenViewModel(private val articleRepository: ArticleRepository) : ViewModel() {
val articles: Flow<PagingData<Article>> = articleRepository.getArticles()
var getArticles by mutableStateOf<List<Article>>(emptyList())
fun setArticleList() {
viewModelScope.launch(Dispatchers.IO) {
getArticles=articleRepository.getAllArticles()
}
}
}

View File

@ -1,8 +1,5 @@
package com.example.pmulabs.viewModels
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.pmulabs.room.models.Comment
@ -10,8 +7,6 @@ import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -22,36 +17,20 @@ class CommentItemViewModel(
suspend fun updateComment(comment: Comment) {
commentRepository.updateComment(comment)
}
var user : User? = null
var commentList by mutableStateOf<Flow<List<Comment>>>(commentRepository.getAllComments())
var commListByArticle by mutableStateOf<List<Comment>>(emptyList())
var userListByArticle by mutableStateOf<List<User>>(emptyList())
suspend fun getCount(articleId: Int): Int = withContext(Dispatchers.IO) {
suspend fun getCount(articleId: Int): Int? = withContext(Dispatchers.IO) {
commentRepository.getCountComment(articleId)
}
fun getUsersByArticle(articleId: Int) {
viewModelScope.launch {
val count=getCount(articleId)
for(item in commentList.first()){
if(item.articleId==articleId){
commListByArticle+=item
}
if(commListByArticle.size>count){
commListByArticle -= commListByArticle[commListByArticle.size-1]
}
}
for(item in commListByArticle){
userListByArticle+=userRepository.getUserById(item.userId).first()
if(userListByArticle.size>count){
userListByArticle -= userListByArticle[userListByArticle.size-1]
}
}
fun getUser(commId: Int) {
viewModelScope.launch(Dispatchers.IO) {
user=userRepository.getUserById(commId)
}
}
suspend fun deleteComment(comment: Comment) {
commentRepository.deleteComment(comment)
}
}
}

View File

@ -7,8 +7,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class CurrentUserViewModel(private val userRepository: UserRepository) : ViewModel(){
@ -21,8 +19,6 @@ class CurrentUserViewModel(private val userRepository: UserRepository) : ViewMod
userid.value = arg.toInt()
viewModelScope.launch {
user = userRepository.getUserById(userid.value)
.filterNotNull()
.first()
}
}
}

View File

@ -7,7 +7,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class EntryScreenViewModel(private val userRepository: UserRepository) : ViewModel() {
@ -15,7 +14,7 @@ class EntryScreenViewModel(private val userRepository: UserRepository) : ViewMod
var userList by mutableStateOf<List<User>>(emptyList())
fun setUserList() {
viewModelScope.launch {
userList=userRepository.getAllUsers().first()
userList=userRepository.getAllUsers()
}
}
}

View File

@ -6,7 +6,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class RegisterScreenViewModel(private val userRepository: UserRepository) : ViewModel() {
@ -16,7 +15,7 @@ class RegisterScreenViewModel(private val userRepository: UserRepository) : View
fun setUserList() {
viewModelScope.launch {
_users.value = userRepository.getAllUsers().first()
_users.value = userRepository.getAllUsers()
}
}

View File

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

View File

@ -3,4 +3,6 @@ plugins {
id("com.android.application") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" 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
}

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