Лабораторная работа №5: Как будто бы готова

This commit is contained in:
Amon 2023-12-22 22:10:53 +04:00
parent 0f8abfbb99
commit 87332767e7
60 changed files with 1031 additions and 398 deletions

View File

@ -2,6 +2,7 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'org.jetbrains.kotlin.plugin.serialization'
}
apply plugin: 'com.android.application'
@ -86,4 +87,9 @@ dependencies {
implementation "androidx.paging:paging-runtime-ktx:$paging_version"
implementation "androidx.paging:paging-compose:$paging_version"
implementation("androidx.room:room-paging:2.5.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
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")
}

View File

@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".DTFApp"
android:allowBackup="true"
@ -11,6 +14,7 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.DTF"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"

View File

@ -2,13 +2,12 @@ package com.example.dtf
import android.app.Application
import androidx.room.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.User
import com.example.dtf.repositories.*
import com.example.dtf.repositories.offline.*
import com.example.dtf.repositories.online.*
import com.example.dtf.repositories.online.mediator.RestCategoryRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -32,21 +31,65 @@ object AppModule {
@Provides
@Singleton
fun provideCategoryRepository(db: AppDatabase) = CategoryRepository(db.categoryDao())
fun provideServerService(): ServerService = ServerService.getInstance()
@Provides
@Singleton
fun provideCommentRepository(db: AppDatabase) = CommentRepository(db.commentDao())
fun provideICategoryRepository(db: AppDatabase, serverService: ServerService): ICategoryRepository = RestCategoryRepository(db, serverService)
@Provides
@Singleton
fun provideLikeRepository(db: AppDatabase) = LikeRepository(db.likeDao())
fun provideICommentRepository(db: AppDatabase, serverService: ServerService): ICommentRepository = RestCommentRepository(db, serverService)
@Provides
@Singleton
fun providePostRepository(db: AppDatabase) = PostRepository(db.postDao())
fun provideILikeRepository(db: AppDatabase, serverService: ServerService): ILikeRepository = RestLikeRepository(db, serverService)
@Provides
@Singleton
fun proviveUserRepository(db: AppDatabase) = UserRepository(db.userDao())
fun provideIPostRepository(db: AppDatabase, serverService: ServerService): IPostRepository = RestPostRepository(db, serverService)
@Provides
@Singleton
fun provideIUserRepository(db: AppDatabase, serverService: ServerService): IUserRepository = RestUserRepository(db, serverService)
@Provides
@Singleton
fun provideOfflineCategoryRepository(db: AppDatabase): OfflineCategoryRepository = OfflineCategoryRepository(db.categoryDao())
@Provides
@Singleton
fun provideOfflineCommentRepository(db: AppDatabase): OfflineCommentRepository = OfflineCommentRepository(db.commentDao())
@Provides
@Singleton
fun provideOfflineLikeRepository(db: AppDatabase): OfflineLikeRepository = OfflineLikeRepository(db.likeDao())
@Provides
@Singleton
fun provideOfflinePostRepository(db: AppDatabase): OfflinePostRepository = OfflinePostRepository(db.postDao())
@Provides
@Singleton
fun provideOfflineUserRepository(db: AppDatabase): OfflineUserRepository = OfflineUserRepository(db.userDao())
@Provides
@Singleton
fun provideRestCategoryRepository(db: AppDatabase, serverService: ServerService): RestCategoryRepository = RestCategoryRepository(db, serverService)
@Provides
@Singleton
fun provideRestCommentRepository(db: AppDatabase, serverService: ServerService): RestCommentRepository = RestCommentRepository(db, serverService)
@Provides
@Singleton
fun provideRestLikeRepository(db: AppDatabase, serverService: ServerService): RestLikeRepository = RestLikeRepository(db, serverService)
@Provides
@Singleton
fun provideRestPostRepository(db: AppDatabase, serverService: ServerService): RestPostRepository = RestPostRepository(db, serverService)
@Provides
@Singleton
fun provideRestUserRepository(db: AppDatabase, serverService: ServerService): RestUserRepository = RestUserRepository(db, serverService)
}

View File

@ -0,0 +1,152 @@
package com.example.dtf.api
import com.example.dtf.dto.Credentials
import com.example.dtf.dto.EditPostDto
import com.example.dtf.dto.MeUser
import com.example.dtf.dto.NewCommentDto
import com.example.dtf.dto.NewPostDto
import com.example.dtf.dto.Token
import com.example.dtf.dto.remote.CategoriesResponse
import com.example.dtf.dto.remote.CommentsResponse
import com.example.dtf.dto.remote.PostsResponse
import com.example.dtf.models.Category
import com.example.dtf.models.Comment
import com.example.dtf.models.Post
import com.example.dtf.models.User
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.http.POST
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.http.Header
interface ServerService {
@POST("auth/login")
suspend fun login(
@Body credentials: Credentials
): Token
@POST("auth/register")
suspend fun register(
@Body credentials: Credentials
): String
@GET("user/me")
suspend fun getCurrentUser(): MeUser
@GET("category")
suspend fun getCategories(
@Query("offset") offset: Int?,
@Query("limit") limit: Int?
): CategoriesResponse
@GET("post")
suspend fun getPosts(
@Query("category") category: Int?,
@Query("offset") offset: Int?,
@Query("limit") limit: Int?
): PostsResponse
@GET("post/{postId}")
suspend fun getPost(
@Path("postId") postId: Int
): Post
@POST("post")
suspend fun createPost(
@Body post: NewPostDto
)
@POST("post/{postId}")
suspend fun updatePost(
@Path("postId") postId: Int,
@Body post: EditPostDto
)
@GET("comment")
suspend fun getComments(
@Query("postId") postId: Int?,
@Query("offset") offset: Int?,
@Query("limit") limit: Int?
): CommentsResponse
@GET("comment/{commentId}")
suspend fun getComment(
@Path("commentId") commentId: Int
): Comment
@POST("comment")
suspend fun createComment(
@Body comment: NewCommentDto
)
@POST("post/{postId}/like")
suspend fun likePost(
@Path("postId") postId: Int
)
@GET("post/{postId}/liked")
suspend fun postIsLiked(
@Path("postId") postId: Int
): Boolean
@GET("post/{postId}/likes")
suspend fun getPostLikes(
@Path("postId") postId: Int
): Int
@GET("user/{userId}")
suspend fun getUser(
@Path("userId") userId: Int
): User
companion object {
private const val BASE_URL = "http://192.168.1.9:8000/"
private var _token: String = ""
@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)
.addInterceptor {
val originalRequest = it.request()
if (_token.isEmpty()) {
it.proceed(originalRequest)
} else {
it.proceed(
originalRequest
.newBuilder()
.header("Authorization", "Bearer $_token")
.method(originalRequest.method, originalRequest.body)
.build()
)
}
}
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
.create(ServerService::class.java)
.also { INSTANCE = it }
}
}
fun setToken(token: String) {
_token = token
}
}
}

View File

@ -1,12 +1,13 @@
package com.example.dtf.dao
import androidx.paging.PagingSource
import androidx.room.*
import com.example.dtf.models.*
import kotlinx.coroutines.flow.Flow
@Dao
interface CategoryDao {
@Insert
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(category: Category)
@Update
@ -16,8 +17,14 @@ interface CategoryDao {
suspend fun delete(category: Category)
@Query("select * from category")
fun getAll() : Flow<List<Category>>
fun getAll() : PagingSource<Int, Category>
@Query("select * from category")
fun getAllCached() : Flow<List<Category>>
@Query("select * from category where category.id = :id")
fun getById(id: Int) : Flow<Category>
@Query("select * from category limit 1")
fun getFirst() : Flow<Category>
}

View File

@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface CommentDao {
@Insert
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(comment: Comment)
@Update
@ -16,12 +16,12 @@ interface CommentDao {
@Delete
suspend fun delete(comment: Comment)
@Query("select * from comment")
fun getAll() : Flow<List<Comment>>
@Query("select * from comment where comment.id = :id")
fun getById(id: Int) : Flow<Comment>
@Query("select * from comment where comment.post_id = :postId ORDER BY date DESC")
@Query("select * from comment where comment.post_id = :postId ORDER BY id DESC")
fun getByPost(postId: Int) : PagingSource<Int, Comment>
@Query("delete from comment where post_id = :query")
fun deleteByQuery(query: String)
}

View File

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface LikeDao {
@Insert
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(like: Like)
@Delete

View File

@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface PostDao {
@Insert
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(post: Post)
@Update
@ -16,12 +16,12 @@ interface PostDao {
@Delete
suspend fun delete(post: Post)
@Query("select * from post")
fun getAll() : Flow<List<Post>>
@Query("select * from post where post.id = :id")
fun getById(id: Int) : Flow<Post>
@Query("select * from post where post.category_id = :categoryId ORDER BY date DESC")
fun getByCategory(categoryId: String) : PagingSource<Int, Post>
@Query("delete from post where category_id = :query")
fun deleteByQuery(query: String)
}

View File

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Insert
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User)
@Update
@ -15,12 +15,6 @@ interface UserDao {
@Delete
suspend fun delete(user: User)
@Query("select * from user order by username collate nocase asc")
fun getAll() : Flow<List<User>>
@Query("select * from user where user.id = :id")
fun getById(id: Int): Flow<User>
@Query("select * from user where user.username = :username and user.password = :password")
fun getByUsernameAndPassword(username: String, password: String): Flow<User?>
}

View File

@ -22,7 +22,7 @@ import java.util.Date
Like::class,
Comment::class
],
version = 2,
version = 4,
exportSchema = false
)
@TypeConverters(Converter::class)

View File

@ -0,0 +1,9 @@
package com.example.dtf.dto
import kotlinx.serialization.Serializable
@Serializable
data class Credentials(
val username: String = "",
val password: String = ""
)

View File

@ -0,0 +1,9 @@
package com.example.dtf.dto
import kotlinx.serialization.Serializable
@Serializable
data class EditPostDto(
val title: String,
val content: String
)

View File

@ -0,0 +1,10 @@
package com.example.dtf.dto
import kotlinx.serialization.Serializable
@Serializable
data class MeUser(
val id: Int,
val username: String,
val is_moderator: Boolean
)

View File

@ -0,0 +1,9 @@
package com.example.dtf.dto
import kotlinx.serialization.Serializable
@Serializable
data class NewCommentDto(
val post_id: Int,
val content: String
)

View File

@ -0,0 +1,10 @@
package com.example.dtf.dto
import kotlinx.serialization.Serializable
@Serializable
data class NewPostDto(
val title: String,
val content: String,
val category_id: Int
)

View File

@ -0,0 +1,8 @@
package com.example.dtf.dto
import kotlinx.serialization.Serializable
@Serializable
data class Token(
val token: String
)

View File

@ -0,0 +1,10 @@
package com.example.dtf.dto.remote
import com.example.dtf.models.Category
import kotlinx.serialization.Serializable
@Serializable
data class CategoriesResponse(
val categories: List<Category>,
val nextKey: Int?
)

View File

@ -0,0 +1,10 @@
package com.example.dtf.dto.remote
import com.example.dtf.models.Comment
import kotlinx.serialization.Serializable
@Serializable
data class CommentsResponse(
val comments: List<Comment>,
val nextKey: Int?
)

View File

@ -0,0 +1,10 @@
package com.example.dtf.dto.remote
import com.example.dtf.models.Post
import kotlinx.serialization.Serializable
@Serializable
data class PostsResponse(
val posts: List<Post>,
val nextKey: Int?
)

View File

@ -1,8 +1,10 @@
package com.example.dtf.models
import androidx.room.*
import kotlinx.serialization.Serializable
@Entity(tableName = "category")
@Serializable
data class Category(
@PrimaryKey(autoGenerate = true)
@ColumnInfo

View File

@ -1,10 +0,0 @@
package com.example.dtf.models
import androidx.room.Embedded
import androidx.room.Relation
data class CategoryWithPosts(
@Embedded val category: Category,
@Relation(parentColumn = "id", entityColumn = "category_id")
val posts: List<Post>
)

View File

@ -1,21 +1,24 @@
package com.example.dtf.models
import androidx.room.*
import java.util.Date
import kotlinx.serialization.Serializable
@Entity(tableName = "comment")
@Serializable
data class Comment(
@PrimaryKey(autoGenerate = true)
@ColumnInfo
val id: Int?,
@ColumnInfo(name = "user_id")
val userId: Int,
val user_id: Int,
@ColumnInfo(name = "post_id")
val postId: Int,
val post_id: Int,
@ColumnInfo
val content: String,
@ColumnInfo
val date: Date
val date: String,
@ColumnInfo
val author: String = ""
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@ -1,9 +1,7 @@
package com.example.dtf.models
import androidx.compose.runtime.collection.MutableVector
import androidx.compose.runtime.collection.mutableVectorOf
import androidx.room.*
import java.util.Date
import kotlinx.serialization.Serializable
//private val posts = listOf(
// Post(
@ -60,11 +58,9 @@ import java.util.Date
//)
@Entity(
tableName = "post",
foreignKeys = [
ForeignKey(User::class, ["id"], ["user_id"])
]
tableName = "post"
)
@Serializable
data class Post(
@PrimaryKey(autoGenerate = true)
@ColumnInfo
@ -74,11 +70,13 @@ data class Post(
@ColumnInfo
val content: String,
@ColumnInfo
val date: Date,
val date: String,
@ColumnInfo(name = "user_id")
val userId: Int,
val user_id: Int,
@ColumnInfo(name = "category_id")
val categoryId: Int
val category_id: Int,
@ColumnInfo
val likes: Int = 0,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@ -1,10 +0,0 @@
package com.example.dtf.models
import androidx.room.Embedded
import androidx.room.Relation
data class PostWithComments(
@Embedded val post: Post,
@Relation(parentColumn = "id", entityColumn = "post_id")
val comments: List<Comment>
)

View File

@ -1,18 +1,16 @@
package com.example.dtf.models
import androidx.room.*
import kotlinx.serialization.Serializable
@Entity(tableName = "user")
@Serializable
data class User(
@PrimaryKey(autoGenerate = true)
@ColumnInfo
val id: Int?,
@ColumnInfo
val username: String,
@ColumnInfo
val password: String,
@ColumnInfo
val isModerator: Boolean
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@ -1,19 +0,0 @@
package com.example.dtf.repositories
import com.example.dtf.dao.CategoryDao
import com.example.dtf.models.Category
import javax.inject.Inject
class CategoryRepository @Inject constructor(
private val categoryDao: CategoryDao
) {
suspend fun insert(category: Category) = categoryDao.insert(category)
suspend fun update(category: Category) = categoryDao.update(category)
suspend fun delete(category: Category) = categoryDao.delete(category)
fun getAll() = categoryDao.getAll()
fun getById(id: Int) = categoryDao.getById(id)
}

View File

@ -1,28 +0,0 @@
package com.example.dtf.repositories
import androidx.paging.*
import com.example.dtf.dao.CommentDao
import com.example.dtf.models.Comment
import javax.inject.Inject
class CommentRepository @Inject constructor(
private val commentDao: CommentDao
) {
suspend fun insert(comment: Comment) = commentDao.insert(comment)
suspend fun update(comment: Comment) = commentDao.update(comment)
suspend fun delete(comment: Comment) = commentDao.delete(comment)
fun getAll() = commentDao.getAll()
fun getById(id: Int) = commentDao.getById(id)
fun getByPost(postId: Int) = Pager(
PagingConfig(
pageSize = 5,
enablePlaceholders = false
),
pagingSourceFactory = { commentDao.getByPost(postId) }
).flow
}

View File

@ -0,0 +1,12 @@
package com.example.dtf.repositories
import androidx.paging.PagingData
import com.example.dtf.models.Category
import kotlinx.coroutines.flow.Flow
interface ICategoryRepository {
fun getAll(): Flow<PagingData<Category>>
fun getAllCached(): Flow<List<Category>>
fun getFirst(): Flow<Category?>
}

View File

@ -0,0 +1,12 @@
package com.example.dtf.repositories
import androidx.paging.PagingData
import com.example.dtf.models.Comment
import kotlinx.coroutines.flow.Flow
interface ICommentRepository {
suspend fun insert(comment: Comment)
fun getById(id: Int): Flow<Comment>
fun getByPost(postId: Int): Flow<PagingData<Comment>>
}

View File

@ -0,0 +1,12 @@
package com.example.dtf.repositories
import com.example.dtf.models.Like
import kotlinx.coroutines.flow.Flow
interface ILikeRepository {
suspend fun insert(like: Like)
suspend fun delete(like: Like)
fun countByPost(postId: Int): Flow<Int>
fun isLikedByUser(userId: Int, postId: Int): Flow<Boolean>
}

View File

@ -0,0 +1,13 @@
package com.example.dtf.repositories
import androidx.paging.PagingData
import com.example.dtf.models.Post
import kotlinx.coroutines.flow.Flow
interface IPostRepository {
suspend fun insert(post: Post)
suspend fun update(post: Post)
fun getById(id: Int): Flow<Post>
fun getByCategory(categoryId: Int): Flow<PagingData<Post>>
}

View File

@ -0,0 +1,8 @@
package com.example.dtf.repositories
import com.example.dtf.models.User
import kotlinx.coroutines.flow.Flow
interface IUserRepository {
fun getById(id: Int) : Flow<User>
}

View File

@ -1,17 +0,0 @@
package com.example.dtf.repositories
import com.example.dtf.dao.LikeDao
import com.example.dtf.models.Like
import javax.inject.Inject
class LikeRepository @Inject constructor(
private val likeDao: LikeDao
) {
suspend fun insert(like: Like) = likeDao.insert(like)
suspend fun delete(like: Like) = likeDao.delete(like)
fun countByPost(postId: Int) = likeDao.countByPost(postId)
fun isLikedByUser(userId: Int, postId: Int) = likeDao.isLikedByUser(userId, postId)
}

View File

@ -1,29 +0,0 @@
package com.example.dtf.repositories
import androidx.paging.*
import com.example.dtf.dao.PostDao
import com.example.dtf.models.Post
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class PostRepository @Inject constructor(
private val postDao: PostDao
) {
suspend fun insert(post: Post) = postDao.insert(post)
suspend fun update(post: Post) = postDao.update(post)
suspend fun delete(post: Post) = postDao.delete(post)
fun getAll() = postDao.getAll()
fun getById(id: Int) = postDao.getById(id)
fun getByCategory(categoryId: Int) = Pager(
PagingConfig(
pageSize = 3,
enablePlaceholders = false
),
pagingSourceFactory = { postDao.getByCategory(categoryId.toString()) }
).flow
}

View File

@ -1,22 +0,0 @@
package com.example.dtf.repositories
import com.example.dtf.dao.UserDao
import com.example.dtf.models.User
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class UserRepository @Inject constructor(
private val userDao: UserDao
) {
suspend fun insert(user: User) = userDao.insert(user)
suspend fun update(user: User) = userDao.update(user)
suspend fun delete(user: User) = userDao.delete(user)
fun getAll() = userDao.getAll()
fun getById(id: Int) = userDao.getById(id)
fun getByUsernameAndPassword(username: String, password: String) = userDao.getByUsernameAndPassword(username, password)
}

View File

@ -0,0 +1,30 @@
package com.example.dtf.repositories.offline
import androidx.paging.Pager
import androidx.paging.PagingConfig
import com.example.dtf.dao.CategoryDao
import com.example.dtf.models.Category
import com.example.dtf.repositories.ICategoryRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class OfflineCategoryRepository @Inject constructor(
private val categoryDao: CategoryDao
) : ICategoryRepository {
override fun getAll() = Pager(
PagingConfig(
pageSize = 5,
enablePlaceholders = false
),
pagingSourceFactory = { categoryDao.getAll() }
).flow
override fun getAllCached(): Flow<List<Category>> {
return categoryDao.getAllCached()
}
override fun getFirst(): Flow<Category?> {
return categoryDao.getFirst()
}
}

View File

@ -0,0 +1,23 @@
package com.example.dtf.repositories.offline
import androidx.paging.*
import com.example.dtf.dao.CommentDao
import com.example.dtf.models.Comment
import com.example.dtf.repositories.ICommentRepository
import javax.inject.Inject
class OfflineCommentRepository @Inject constructor(
private val commentDao: CommentDao
) : ICommentRepository {
override suspend fun insert(comment: Comment) = commentDao.insert(comment)
override fun getById(id: Int) = commentDao.getById(id)
override fun getByPost(postId: Int) = Pager(
PagingConfig(
pageSize = 5,
enablePlaceholders = false
),
pagingSourceFactory = { commentDao.getByPost(postId) }
).flow
}

View File

@ -0,0 +1,18 @@
package com.example.dtf.repositories.offline
import com.example.dtf.dao.LikeDao
import com.example.dtf.models.Like
import com.example.dtf.repositories.ILikeRepository
import javax.inject.Inject
class OfflineLikeRepository @Inject constructor(
private val likeDao: LikeDao
) : ILikeRepository {
override suspend fun insert(like: Like) = likeDao.insert(like)
override suspend fun delete(like: Like) = likeDao.delete(like)
override fun countByPost(postId: Int) = likeDao.countByPost(postId)
override fun isLikedByUser(userId: Int, postId: Int) = likeDao.isLikedByUser(userId, postId)
}

View File

@ -0,0 +1,25 @@
package com.example.dtf.repositories.offline
import androidx.paging.*
import com.example.dtf.dao.PostDao
import com.example.dtf.models.Post
import com.example.dtf.repositories.IPostRepository
import javax.inject.Inject
class OfflinePostRepository @Inject constructor(
private val postDao: PostDao
) : IPostRepository {
override suspend fun insert(post: Post) = postDao.insert(post)
override suspend fun update(post: Post) = postDao.update(post)
override fun getById(id: Int) = postDao.getById(id)
override fun getByCategory(categoryId: Int) = Pager(
PagingConfig(
pageSize = 3,
enablePlaceholders = false
),
pagingSourceFactory = { postDao.getByCategory(categoryId.toString()) }
).flow
}

View File

@ -0,0 +1,12 @@
package com.example.dtf.repositories.offline
import com.example.dtf.dao.UserDao
import com.example.dtf.models.User
import com.example.dtf.repositories.IUserRepository
import javax.inject.Inject
class OfflineUserRepository @Inject constructor(
private val userDao: UserDao
) : IUserRepository {
override fun getById(id: Int) = userDao.getById(id)
}

View File

@ -0,0 +1,37 @@
package com.example.dtf.repositories.online.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.Category
import com.example.dtf.repositories.ICategoryRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
class RestCategoryRepository @Inject constructor(
private val appDatabase: AppDatabase,
private val serverService: ServerService
) : ICategoryRepository {
@OptIn(ExperimentalPagingApi::class)
override fun getAll(): Flow<PagingData<Category>> = Pager(
PagingConfig(
pageSize = 3,
enablePlaceholders = false
),
remoteMediator = CategoryMediator(appDatabase, serverService),
pagingSourceFactory = { appDatabase.categoryDao().getAll() }
).flow
override fun getAllCached(): Flow<List<Category>> {
return appDatabase.categoryDao().getAllCached()
}
override fun getFirst(): Flow<Category?> {
return appDatabase.categoryDao().getFirst()
}
}

View File

@ -0,0 +1,41 @@
package com.example.dtf.repositories.online
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.dto.NewCommentDto
import com.example.dtf.models.Comment
import com.example.dtf.repositories.ICommentRepository
import com.example.dtf.repositories.online.mediator.CommentMediator
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
class RestCommentRepository @Inject constructor(
private val appDatabase: AppDatabase,
private val serverService: ServerService
) : ICommentRepository {
override suspend fun insert(comment: Comment) {
serverService.createComment(
comment = NewCommentDto(post_id = comment.post_id, content = comment.content)
)
appDatabase.commentDao().insert(comment)
}
override fun getById(id: Int): Flow<Comment> {
return flow { emit(serverService.getComment(id)) }
}
@OptIn(ExperimentalPagingApi::class)
override fun getByPost(postId: Int): Flow<PagingData<Comment>> = Pager(
PagingConfig(
pageSize = 5,
enablePlaceholders = false,
),
remoteMediator = CommentMediator(appDatabase, serverService, postId.toString()),
pagingSourceFactory = { appDatabase.commentDao().getByPost(postId) }
).flow
}

View File

@ -0,0 +1,30 @@
package com.example.dtf.repositories.online
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.Like
import com.example.dtf.repositories.ILikeRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
class RestLikeRepository @Inject constructor(
private val appDatabase: AppDatabase,
private val serverService: ServerService
) : ILikeRepository {
override suspend fun insert(like: Like) {
serverService.likePost(like.postId)
}
override suspend fun delete(like: Like) {
serverService.likePost(like.postId)
}
override fun countByPost(postId: Int): Flow<Int> {
return flow { emit(serverService.getPostLikes(postId)) }
}
override fun isLikedByUser(userId: Int, postId: Int): Flow<Boolean> {
return flow { emit(serverService.postIsLiked(postId)) }
}
}

View File

@ -0,0 +1,49 @@
package com.example.dtf.repositories.online
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.dto.EditPostDto
import com.example.dtf.dto.NewPostDto
import com.example.dtf.models.Post
import com.example.dtf.repositories.IPostRepository
import com.example.dtf.repositories.online.mediator.PostMediator
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
class RestPostRepository @Inject constructor(
private val appDatabase: AppDatabase,
private val serverService: ServerService
) : IPostRepository {
override suspend fun insert(post: Post) {
serverService.createPost(
post = NewPostDto(post.title, post.content, post.category_id)
)
}
override suspend fun update(post: Post) {
serverService.updatePost(
postId = post.id!!,
post = EditPostDto(post.title, post.content)
)
appDatabase.postDao().update(post)
}
override fun getById(id: Int): Flow<Post> {
return flow { emit(serverService.getPost(id)) }
}
@OptIn(ExperimentalPagingApi::class)
override fun getByCategory(categoryId: Int): Flow<PagingData<Post>> = Pager(
PagingConfig(
pageSize = 4,
enablePlaceholders = false
),
remoteMediator = PostMediator(appDatabase, serverService, categoryId.toString()),
pagingSourceFactory = { appDatabase.postDao().getByCategory(categoryId.toString()) }
).flow
}

View File

@ -0,0 +1,18 @@
package com.example.dtf.repositories.online
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.User
import com.example.dtf.repositories.IUserRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
class RestUserRepository @Inject constructor(
private val appDatabase: AppDatabase,
private val serverService: ServerService
) : IUserRepository {
override fun getById(id: Int): Flow<User> {
return flow { emit(serverService.getUser(id)) }
}
}

View File

@ -0,0 +1,63 @@
package com.example.dtf.repositories.online.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.Category
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class CategoryMediator (
private val database: AppDatabase,
private val serverService: ServerService
) : RemoteMediator<Int, Category>(){
private val categoryDao = database.categoryDao()
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Category>
): MediatorResult {
return try {
var loadKey = when (loadType) {
LoadType.REFRESH -> null
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val lastItem = state.lastItemOrNull()
?: return MediatorResult.Success(endOfPaginationReached = true)
lastItem.id
}
}
if (loadKey == null) {
loadKey = 0
}
val response = serverService.getCategories(
offset = loadKey,
limit = state.config.pageSize
)
database.withTransaction {
if (loadType == LoadType.REFRESH) {
for (category in response.categories) {
categoryDao.delete(category)
}
}
for (category in response.categories) {
categoryDao.insert(category)
}
}
MediatorResult.Success(endOfPaginationReached = response.nextKey == null)
} catch (e: IOException) {
MediatorResult.Error(e)
}
}
}

View File

@ -0,0 +1,63 @@
package com.example.dtf.repositories.online.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.Comment
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class CommentMediator (
private val database: AppDatabase,
private val serverService: ServerService,
private val query: String
) : RemoteMediator<Int, Comment>(){
private val commentDao = database.commentDao()
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Comment>
): MediatorResult {
return try {
var loadKey = when (loadType) {
LoadType.REFRESH -> null
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val lastItem = state.lastItemOrNull()
?: return MediatorResult.Success(endOfPaginationReached = true)
lastItem.id
}
}
if (loadKey == null) {
loadKey = 0
}
val response = serverService.getComments(
postId = query.toInt(),
offset = loadKey,
limit = state.config.pageSize
)
database.withTransaction {
if (loadType == LoadType.REFRESH) {
commentDao.deleteByQuery(query)
}
for (comment in response.comments) {
commentDao.insert(comment)
}
}
MediatorResult.Success(endOfPaginationReached = response.nextKey == null)
} catch (e: IOException) {
MediatorResult.Error(e)
}
}
}

View File

@ -0,0 +1,63 @@
package com.example.dtf.repositories.online.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.dtf.api.ServerService
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.Post
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class PostMediator (
private val database: AppDatabase,
private val serverService: ServerService,
private val query: String
) : RemoteMediator<Int, Post>(){
private val postDao = database.postDao()
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Post>
): MediatorResult {
return try {
var loadKey = when (loadType) {
LoadType.REFRESH -> null
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val lastItem = state.lastItemOrNull()
?: return MediatorResult.Success(endOfPaginationReached = true)
lastItem.id
}
}
if (loadKey == null) {
loadKey = 0
}
val response = serverService.getPosts(
category = query.toInt(),
offset = loadKey,
limit = state.config.pageSize
)
database.withTransaction {
if (loadType == LoadType.REFRESH) {
postDao.deleteByQuery(query)
}
for (comment in response.posts) {
postDao.insert(comment)
}
}
MediatorResult.Success(endOfPaginationReached = response.nextKey == null)
} catch (e: IOException) {
MediatorResult.Error(e)
}
}
}

View File

@ -18,6 +18,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.*
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.dtf.PreferencesManager
import com.example.dtf.models.Category
import com.example.dtf.utils.ScreenPaths
@ -36,9 +37,7 @@ fun NewPostScreen(navController: NavHostController) {
val viewModel = hiltViewModel<NewPostViewModel>()
val success = viewModel.addingPostState.observeAsState().value
val categories = viewModel.categories.observeAsState().value
viewModel.retrieveCategories()
val categories = viewModel.getCategories().collectAsState(initial = listOf())
if (success == true) {
navController.navigate(ScreenPaths.Posts.route) {
@ -91,7 +90,9 @@ fun NewPostScreen(navController: NavHostController) {
colors = ButtonDefaults.buttonColors(Color(0xFFFFFFFF)),
border = BorderStroke(1.dp, Color(0xFF0085FF)),
shape = RoundedCornerShape(15.dp),
modifier = Modifier.fillMaxWidth().padding(15.dp, 10.dp, 15.dp, 0.dp)
modifier = Modifier
.fillMaxWidth()
.padding(15.dp, 10.dp, 15.dp, 0.dp)
) {
Text(
text = selectedCategory.value.name,
@ -102,11 +103,13 @@ fun NewPostScreen(navController: NavHostController) {
)
)
DropdownMenu(
modifier = Modifier.fillMaxWidth().padding(horizontal = 15.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 15.dp),
expanded = expanded.value,
onDismissRequest = {expanded.value = false}
) {
categories?.forEach { category ->
categories.value.forEach { category ->
DropdownMenuItem(
onClick = {
selectedCategory.value = category
@ -126,17 +129,23 @@ fun NewPostScreen(navController: NavHostController) {
value = title,
"Заголовок",
onChanged = {title.value = it},
modifier = Modifier.fillMaxWidth().padding(horizontal = 15.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 15.dp),
backgroundColor = Color(0xFFFEFEFE)
)
MyTextField(
value = content,
"Содержимое",
onChanged = {content.value = it},
modifier = Modifier.fillMaxWidth().heightIn(min = 500.dp).padding(horizontal = 15.dp),
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 400.dp)
.padding(horizontal = 15.dp),
backgroundColor = Color(0xFFFEFEFE),
false
)
Button(
{
if (selectedCategory.value.id != null) {

View File

@ -19,23 +19,14 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.*
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.example.dtf.PreferencesManager
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.Comment
import com.example.dtf.models.Post
import com.example.dtf.models.PostWithComments
import com.example.dtf.models.User
import com.example.dtf.utils.ScreenPaths
import com.example.dtf.viewmodels.PostViewModel
import com.example.dtf.widgets.MyTextField
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Date
@Composable
fun PostScreen(postId: Int, navController: NavHostController) {
@ -89,16 +80,7 @@ fun PostScreen(postId: Int, navController: NavHostController) {
) {
if (post != null) {
Text(
text = "day.month.year".replace(
"day",
post.date.day.toString()
).replace(
"month",
post.date.month.toString()
).replace(
"year",
post.date.year.toString()
),
text = post.date,
fontSize = 14.sp,
color = Color(0xFFCECCCC)
)
@ -115,16 +97,7 @@ fun PostScreen(postId: Int, navController: NavHostController) {
)
}
Row (
modifier = Modifier.clickable {
if (isLiked.value) {
viewModel.unlikePost(sharedPref, postId)
likes.intValue--
} else {
viewModel.likePost(sharedPref, postId)
likes.intValue++
}
isLiked.value = !isLiked.value
},
modifier = Modifier,
horizontalArrangement = Arrangement.End
) {
Text(
@ -133,7 +106,16 @@ fun PostScreen(postId: Int, navController: NavHostController) {
color = Color.Green
)
Icon(
modifier = Modifier.padding(start = 8.dp),
modifier = Modifier.padding(start = 8.dp).clickable {
if (isLiked.value) {
viewModel.unlikePost(sharedPref, postId)
likes.intValue--
} else {
viewModel.likePost(sharedPref, postId)
likes.intValue++
}
isLiked.value = !isLiked.value
},
imageVector = Icons.Default.ThumbUp,
contentDescription = null,
tint = if (isLiked.value) { Color(40, 200, 40, 255) } else {Color.Black}
@ -202,20 +184,11 @@ private fun Comment(comment: Comment) {
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = user?.username ?: "Loading...",
text = comment.author.ifEmpty { user?.username ?: "Loading..." },
fontSize = 20.sp
)
Text(
text = "day.month.year".replace(
"day",
comment.date.day.toString()
).replace(
"month",
comment.date.month.toString()
).replace(
"year",
comment.date.year.toString()
),
text = comment.date,
fontSize = 14.sp,
color = Color(0xFFCECCCC)
)

View File

@ -3,6 +3,7 @@ package com.example.dtf.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ThumbUp
@ -18,21 +19,14 @@ import androidx.compose.ui.unit.*
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.example.dtf.PreferencesManager
import com.example.dtf.db.AppDatabase
import com.example.dtf.models.Category
import com.example.dtf.models.CategoryWithPosts
import com.example.dtf.models.Post
import com.example.dtf.utils.ScreenPaths
import com.example.dtf.viewmodels.PostsViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun PostsScreen(navController: NavHostController) {
@ -47,19 +41,8 @@ fun PostsScreen(navController: NavHostController) {
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.1f)
.horizontalScroll(
rememberScrollState()
)
.background(Color.White),
horizontalArrangement = Arrangement.spacedBy(25.dp),
verticalAlignment = Alignment.CenterVertically
) {
Categories(viewModel, currentCategory, posts)
}
Categories(viewModel, currentCategory, posts)
Row(
modifier = Modifier.fillMaxSize()
) {
@ -72,36 +55,50 @@ fun PostsScreen(navController: NavHostController) {
@Composable
private fun Categories(viewModel: PostsViewModel, currentCategory: MutableState<Category?>, posts: MutableState<Flow<PagingData<Post>>?>) {
val categories = viewModel.getCategories().collectAsState(listOf()).value
val categories = viewModel.getCategoriesListUiState().collectAsLazyPagingItems()
if (categories.isNotEmpty() && currentCategory.value == null) {
currentCategory.value = categories[0]
posts.value = viewModel.getPostsListUiState(currentCategory.value!!.id!!)
}
Spacer(modifier = Modifier.width(5.dp))
categories.forEach {category ->
Text(
modifier = Modifier
.clickable {
currentCategory.value = category
posts.value = viewModel.getPostsListUiState(category.id!!)
}
.drawBehind {
if (category.name == currentCategory.value?.name) {
val strokeWidthPx = 2.dp.toPx()
val verticalOffset = size.height + 2.sp.toPx()
drawLine(
color = Color(0xFF319CFF),
strokeWidth = strokeWidthPx,
start = Offset(0f, verticalOffset),
end = Offset(size.width, verticalOffset)
)
LazyRow(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.1f)
.background(Color.White),
horizontalArrangement = Arrangement.spacedBy(25.dp),
verticalAlignment = Alignment.CenterVertically
) {
item {
Spacer(modifier = Modifier.width(5.dp))
}
items(
count = categories.itemCount,
key = categories.itemKey()
) {
if (currentCategory.value == null) {
currentCategory.value = categories[0]
posts.value = viewModel.getPostsListUiState(currentCategory.value!!.id!!)
}
Text(
modifier = Modifier
.clickable {
currentCategory.value = categories[it]!!
posts.value = viewModel.getPostsListUiState(categories[it]!!.id!!)
}
},
text = category.name,
fontSize = 22.sp
)
.drawBehind {
if (categories[it]!!.name == currentCategory.value?.name) {
val strokeWidthPx = 2.dp.toPx()
val verticalOffset = size.height + 2.sp.toPx()
drawLine(
color = Color(0xFF319CFF),
strokeWidth = strokeWidthPx,
start = Offset(0f, verticalOffset),
end = Offset(size.width, verticalOffset)
)
}
},
text = categories[it]!!.name,
fontSize = 22.sp
)
}
}
}
@ -129,7 +126,7 @@ private fun PostsByCategory(viewModel: PostsViewModel, navController: NavHostCon
private fun Post(viewModel: PostsViewModel, navController: NavHostController, post: Post) {
val sharedPref = PreferencesManager(LocalContext.current)
val likes = remember { mutableIntStateOf(0) }
val likes = remember { mutableIntStateOf(post.likes) }
val isLiked = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
@ -162,41 +159,28 @@ private fun Post(viewModel: PostsViewModel, navController: NavHostController, po
fontSize = 26.sp
)
Text(
modifier = Modifier.fillMaxHeight(0.6f).padding(10.dp, 0.dp, 10.dp, 0.dp),
modifier = Modifier
.fillMaxHeight(0.6f)
.padding(10.dp, 0.dp, 10.dp, 0.dp),
text = post.content,
fontSize = 20.sp,
overflow = TextOverflow.Ellipsis
)
Row(
modifier = Modifier.fillMaxWidth().padding(10.dp),
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "day.month.year".replace(
"day",
post.date.day.toString()
).replace(
"month",
post.date.month.toString()
).replace(
"year",
post.date.year.toString()
),
text = post.date,
fontSize = 14.sp,
color = Color(0xFFCECCCC)
)
Row (
modifier = Modifier.fillMaxWidth().clickable {
if (isLiked.value) {
viewModel.unlikePost(sharedPref, post.id!!)
likes.intValue--
} else {
viewModel.likePost(sharedPref, post.id!!)
likes.intValue++
}
isLiked.value = !isLiked.value
},
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Text(
@ -205,7 +189,18 @@ private fun Post(viewModel: PostsViewModel, navController: NavHostController, po
color = Color.Green
)
Icon(
modifier = Modifier.padding(start = 8.dp),
modifier = Modifier
.padding(start = 8.dp)
.clickable {
if (isLiked.value) {
viewModel.unlikePost(sharedPref, post.id!!)
likes.intValue--
} else {
viewModel.likePost(sharedPref, post.id!!)
likes.intValue++
}
isLiked.value = !isLiked.value
},
imageVector = Icons.Default.ThumbUp,
contentDescription = null,
tint = if (isLiked.value) { Color(40, 200, 40, 255) } else {Color.Black}

View File

@ -6,14 +6,14 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.dtf.PreferencesManager
import com.example.dtf.models.Post
import com.example.dtf.repositories.PostRepository
import com.example.dtf.repositories.IPostRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class EditPostViewModel @Inject constructor(
private val postRepository: PostRepository
private val postRepository: IPostRepository
) : ViewModel() {
private val _editingPostState = MutableLiveData<Boolean?>(null)
val editingPostState: LiveData<Boolean?>
@ -41,7 +41,7 @@ class EditPostViewModel @Inject constructor(
viewModelScope.launch {
if (title.isNotEmpty() && content.isNotEmpty()) {
postRepository.getById(postId).collect {
postRepository.update(Post(postId, title, content, it.date, it.userId, it.categoryId))
postRepository.update(Post(postId, title, content, it.date, it.user_id, it.category_id))
_editingPostState.postValue(true)
}
} else {

View File

@ -5,55 +5,44 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.dtf.PreferencesManager
import com.example.dtf.models.Category
import com.example.dtf.models.User
import com.example.dtf.repositories.CategoryRepository
import com.example.dtf.repositories.UserRepository
import com.example.dtf.api.ServerService
import com.example.dtf.dto.Credentials
import com.example.dtf.repositories.IUserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository,
private val categoryRepository: CategoryRepository
private val serverService: ServerService,
) : ViewModel() {
private val _successState = MutableLiveData<Boolean?>()
val successState: LiveData<Boolean?>
get() = _successState
init {
addAdmin()
}
fun calmSuccessState() {
_successState.postValue(null)
}
fun login(sharedPref: PreferencesManager, username: String, password: String) {
viewModelScope.launch {
userRepository.getByUsernameAndPassword(username, password).collect {
if (it == null) {
_successState.postValue(false)
} else {
sharedPref.saveData("userId", it.id.toString())
sharedPref.saveData("isModerator", it.isModerator.toString())
_successState.postValue(true)
}
}
}
}
val token = serverService.login(Credentials(username, password))
private fun addAdmin() {
viewModelScope.launch {
userRepository.getAll().collect {
if (it.size == 0) {
userRepository.insert(User(1, "admin", "admin", true))
categoryRepository.insert(Category(1, "Аниме"))
categoryRepository.insert(Category(2, "Игры"))
categoryRepository.insert(Category(3, "Фильмы"))
}
if (token.token.isEmpty()) {
_successState.postValue(false)
return@launch
}
ServerService.setToken(token.token)
val user = serverService.getCurrentUser()
sharedPref.saveData("token", token.token)
sharedPref.saveData("username", user.username)
sharedPref.saveData("isModerator", user.is_moderator.toString())
sharedPref.saveData("userId", user.id.toString())
_successState.postValue(true)
}
}
}

View File

@ -5,36 +5,24 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.dtf.PreferencesManager
import com.example.dtf.models.Category
import com.example.dtf.models.Post
import com.example.dtf.repositories.CategoryRepository
import com.example.dtf.repositories.PostRepository
import com.example.dtf.repositories.ICategoryRepository
import com.example.dtf.repositories.IPostRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import java.time.LocalDate
import java.util.Date
import javax.inject.Inject
@HiltViewModel
class NewPostViewModel @Inject constructor(
private val postRepository: PostRepository,
private val categoryRepository: CategoryRepository
private val postRepository: IPostRepository,
private val categoryRepository: ICategoryRepository
) : ViewModel() {
private val _addingPostState = MutableLiveData<Boolean?>(null)
val addingPostState: LiveData<Boolean?>
get() = _addingPostState
private val _categories = MutableLiveData<List<Category>>(listOf())
val categories: LiveData<List<Category>>
get() = _categories
fun retrieveCategories() {
viewModelScope.launch {
categoryRepository.getAll().collect {
_categories.postValue(it)
}
}
}
fun getCategories() = categoryRepository.getAllCached()
fun calmAddingState() {
_addingPostState.value = null
@ -50,7 +38,7 @@ class NewPostViewModel @Inject constructor(
null,
title,
content,
Date(),
"${Date().year + 1900}-${Date().month + 1}-${Date().date}",
sharedPref.getData("userId", "0").toInt(),
categoryId
)

View File

@ -4,16 +4,15 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.PagingData
import com.example.dtf.PreferencesManager
import com.example.dtf.models.Comment
import com.example.dtf.models.Like
import com.example.dtf.models.Post
import com.example.dtf.repositories.CommentRepository
import com.example.dtf.repositories.LikeRepository
import com.example.dtf.repositories.PostRepository
import com.example.dtf.repositories.UserRepository
import com.example.dtf.repositories.ICommentRepository
import com.example.dtf.repositories.ILikeRepository
import com.example.dtf.repositories.IPostRepository
import com.example.dtf.repositories.IUserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
@ -22,10 +21,10 @@ import javax.inject.Inject
@HiltViewModel
class PostViewModel @Inject constructor(
private val postRepository: PostRepository,
private val commentRepository: CommentRepository,
private val likeRepository: LikeRepository,
private val userRepository: UserRepository
private val postRepository: IPostRepository,
private val commentRepository: ICommentRepository,
private val likeRepository: ILikeRepository,
private val userRepository: IUserRepository
) : ViewModel() {
private val _post = MutableLiveData<Post>()
val post: LiveData<Post>
@ -79,13 +78,14 @@ class PostViewModel @Inject constructor(
userId,
postId,
content,
Date()
"${Date().year + 1900}-${Date().month + 1}-${Date().date}",
sharedPref.getData("username", "Unknown")
)
)
}
}
fun getCommentsAuthor(comment: Comment) = userRepository.getById(comment.userId)
fun getCommentsAuthor(comment: Comment) = userRepository.getById(comment.user_id)
fun getCommentsListUiState(postId: Int): Flow<PagingData<Comment>> = commentRepository.getByPost(postId)
}

View File

@ -4,18 +4,18 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.dtf.PreferencesManager
import com.example.dtf.models.Like
import com.example.dtf.repositories.CategoryRepository
import com.example.dtf.repositories.LikeRepository
import com.example.dtf.repositories.PostRepository
import com.example.dtf.repositories.ICategoryRepository
import com.example.dtf.repositories.ILikeRepository
import com.example.dtf.repositories.IPostRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class PostsViewModel @Inject constructor(
private val postRepository: PostRepository,
private val categoryRepository: CategoryRepository,
private val likeRepository: LikeRepository
private val postRepository: IPostRepository,
private val categoryRepository: ICategoryRepository,
private val likeRepository: ILikeRepository
) : ViewModel() {
fun getLikes(postId: Int) = likeRepository.countByPost(postId)
@ -45,7 +45,9 @@ class PostsViewModel @Inject constructor(
postId
)
fun getCategories() = categoryRepository.getAll()
fun getCategoriesListUiState() = categoryRepository.getAll()
fun getInitialCategory() = categoryRepository.getFirst()
fun getPostsListUiState(categoryId: Int) = postRepository.getByCategory(categoryId)
}

View File

@ -6,33 +6,26 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.dtf.PreferencesManager
import com.example.dtf.models.User
import com.example.dtf.repositories.UserRepository
import com.example.dtf.repositories.offline.OfflineUserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val userRepository: UserRepository
private val userRepository: OfflineUserRepository
) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User>
get() = _user
fun retrieveUser(sharedPref: PreferencesManager) {
val userId = sharedPref.getData("userId", "-1").toInt()
if (userId == -1) return
viewModelScope.launch {
userRepository.getById(userId).collect {
_user.postValue(it)
}
}
_user.postValue(User(null, sharedPref.getData("username", "Nickname")))
}
fun logout(sharedPref: PreferencesManager) {
sharedPref.deleteData("userId")
sharedPref.deleteData("token")
sharedPref.deleteData("username")
sharedPref.deleteData("isModerator")
}
}

View File

@ -4,16 +4,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.dtf.PreferencesManager
import com.example.dtf.models.User
import com.example.dtf.repositories.UserRepository
import com.example.dtf.api.ServerService
import com.example.dtf.dto.Credentials
import com.example.dtf.repositories.offline.OfflineUserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class RegisterViewModel @Inject constructor(
private val userRepository: UserRepository
private val serverService: ServerService
) : ViewModel() {
private val _successState = MutableLiveData<Boolean?>()
val successState: LiveData<Boolean?>
@ -38,15 +38,11 @@ class RegisterViewModel @Inject constructor(
_successState.postValue(false)
return
}
viewModelScope.launch {
userRepository.getByUsernameAndPassword(username, password).collect {
if (it != null) {
_successState.postValue(false)
} else {
userRepository.insert(User(null, username, password, false))
_successState.postValue(true)
}
if (serverService.register(Credentials(username, password)) == "NOT OK") {
_successState.postValue(false)
} else {
_successState.postValue(true)
}
}
}

View File

@ -14,4 +14,6 @@ plugins {
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.0' apply false
id 'org.jetbrains.kotlin.jvm' version '1.7.0' apply false
}

View File

@ -4,5 +4,5 @@
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Tue Nov 07 09:36:13 SAMT 2023
sdk.dir=C\:\\Users\\Aqua\\AppData\\Local\\Android\\Sdk
#Fri Dec 22 01:26:17 GMT+04:00 2023
sdk.dir=D\:\\Programs\\Android