diff --git a/app/build.gradle b/app/build.gradle index db00a26..b4c0ad2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7b80306..f5b4ac4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,9 @@ + + + > + fun getAll() : PagingSource + + @Query("select * from category") + fun getAllCached() : Flow> @Query("select * from category where category.id = :id") fun getById(id: Int) : Flow + + @Query("select * from category limit 1") + fun getFirst() : Flow } diff --git a/app/src/main/java/com/example/dtf/dao/CommentDao.kt b/app/src/main/java/com/example/dtf/dao/CommentDao.kt index 36a7eb1..d89a301 100644 --- a/app/src/main/java/com/example/dtf/dao/CommentDao.kt +++ b/app/src/main/java/com/example/dtf/dao/CommentDao.kt @@ -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> - @Query("select * from comment where comment.id = :id") fun getById(id: Int) : Flow - @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 + + @Query("delete from comment where post_id = :query") + fun deleteByQuery(query: String) } diff --git a/app/src/main/java/com/example/dtf/dao/LikeDao.kt b/app/src/main/java/com/example/dtf/dao/LikeDao.kt index 20e8d00..183f731 100644 --- a/app/src/main/java/com/example/dtf/dao/LikeDao.kt +++ b/app/src/main/java/com/example/dtf/dao/LikeDao.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow @Dao interface LikeDao { - @Insert + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(like: Like) @Delete diff --git a/app/src/main/java/com/example/dtf/dao/PostDao.kt b/app/src/main/java/com/example/dtf/dao/PostDao.kt index d7edb53..c2a762b 100644 --- a/app/src/main/java/com/example/dtf/dao/PostDao.kt +++ b/app/src/main/java/com/example/dtf/dao/PostDao.kt @@ -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> - @Query("select * from post where post.id = :id") fun getById(id: Int) : Flow @Query("select * from post where post.category_id = :categoryId ORDER BY date DESC") fun getByCategory(categoryId: String) : PagingSource + + @Query("delete from post where category_id = :query") + fun deleteByQuery(query: String) } diff --git a/app/src/main/java/com/example/dtf/dao/UserDao.kt b/app/src/main/java/com/example/dtf/dao/UserDao.kt index 60ecc10..de080c6 100644 --- a/app/src/main/java/com/example/dtf/dao/UserDao.kt +++ b/app/src/main/java/com/example/dtf/dao/UserDao.kt @@ -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> - @Query("select * from user where user.id = :id") fun getById(id: Int): Flow - - @Query("select * from user where user.username = :username and user.password = :password") - fun getByUsernameAndPassword(username: String, password: String): Flow } \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/db/AppDatabase.kt b/app/src/main/java/com/example/dtf/db/AppDatabase.kt index 161f1f8..b3cd4d5 100644 --- a/app/src/main/java/com/example/dtf/db/AppDatabase.kt +++ b/app/src/main/java/com/example/dtf/db/AppDatabase.kt @@ -22,7 +22,7 @@ import java.util.Date Like::class, Comment::class ], - version = 2, + version = 4, exportSchema = false ) @TypeConverters(Converter::class) diff --git a/app/src/main/java/com/example/dtf/dto/Credentials.kt b/app/src/main/java/com/example/dtf/dto/Credentials.kt new file mode 100644 index 0000000..e3a2c35 --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/Credentials.kt @@ -0,0 +1,9 @@ +package com.example.dtf.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class Credentials( + val username: String = "", + val password: String = "" +) diff --git a/app/src/main/java/com/example/dtf/dto/EditPostDto.kt b/app/src/main/java/com/example/dtf/dto/EditPostDto.kt new file mode 100644 index 0000000..de4923d --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/EditPostDto.kt @@ -0,0 +1,9 @@ +package com.example.dtf.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class EditPostDto( + val title: String, + val content: String +) diff --git a/app/src/main/java/com/example/dtf/dto/MeUser.kt b/app/src/main/java/com/example/dtf/dto/MeUser.kt new file mode 100644 index 0000000..54eb68e --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/MeUser.kt @@ -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 +) diff --git a/app/src/main/java/com/example/dtf/dto/NewCommentDto.kt b/app/src/main/java/com/example/dtf/dto/NewCommentDto.kt new file mode 100644 index 0000000..d697e5b --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/NewCommentDto.kt @@ -0,0 +1,9 @@ +package com.example.dtf.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class NewCommentDto( + val post_id: Int, + val content: String +) diff --git a/app/src/main/java/com/example/dtf/dto/NewPostDto.kt b/app/src/main/java/com/example/dtf/dto/NewPostDto.kt new file mode 100644 index 0000000..84bbe42 --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/NewPostDto.kt @@ -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 +) diff --git a/app/src/main/java/com/example/dtf/dto/Token.kt b/app/src/main/java/com/example/dtf/dto/Token.kt new file mode 100644 index 0000000..f200831 --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/Token.kt @@ -0,0 +1,8 @@ +package com.example.dtf.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class Token( + val token: String +) diff --git a/app/src/main/java/com/example/dtf/dto/remote/CategoriesResponse.kt b/app/src/main/java/com/example/dtf/dto/remote/CategoriesResponse.kt new file mode 100644 index 0000000..99c225f --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/remote/CategoriesResponse.kt @@ -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, + val nextKey: Int? +) diff --git a/app/src/main/java/com/example/dtf/dto/remote/CommentsResponse.kt b/app/src/main/java/com/example/dtf/dto/remote/CommentsResponse.kt new file mode 100644 index 0000000..03daa5a --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/remote/CommentsResponse.kt @@ -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, + val nextKey: Int? +) diff --git a/app/src/main/java/com/example/dtf/dto/remote/PostsResponse.kt b/app/src/main/java/com/example/dtf/dto/remote/PostsResponse.kt new file mode 100644 index 0000000..654df0e --- /dev/null +++ b/app/src/main/java/com/example/dtf/dto/remote/PostsResponse.kt @@ -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, + val nextKey: Int? +) diff --git a/app/src/main/java/com/example/dtf/models/Category.kt b/app/src/main/java/com/example/dtf/models/Category.kt index 9700eca..2df43fd 100644 --- a/app/src/main/java/com/example/dtf/models/Category.kt +++ b/app/src/main/java/com/example/dtf/models/Category.kt @@ -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 diff --git a/app/src/main/java/com/example/dtf/models/CategoryWithPosts.kt b/app/src/main/java/com/example/dtf/models/CategoryWithPosts.kt deleted file mode 100644 index f666c25..0000000 --- a/app/src/main/java/com/example/dtf/models/CategoryWithPosts.kt +++ /dev/null @@ -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 -) diff --git a/app/src/main/java/com/example/dtf/models/Comment.kt b/app/src/main/java/com/example/dtf/models/Comment.kt index fc3c6d7..203fb7f 100644 --- a/app/src/main/java/com/example/dtf/models/Comment.kt +++ b/app/src/main/java/com/example/dtf/models/Comment.kt @@ -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 diff --git a/app/src/main/java/com/example/dtf/models/Post.kt b/app/src/main/java/com/example/dtf/models/Post.kt index 25c3b6a..5cd092f 100644 --- a/app/src/main/java/com/example/dtf/models/Post.kt +++ b/app/src/main/java/com/example/dtf/models/Post.kt @@ -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 diff --git a/app/src/main/java/com/example/dtf/models/PostWithComments.kt b/app/src/main/java/com/example/dtf/models/PostWithComments.kt deleted file mode 100644 index ef64a9a..0000000 --- a/app/src/main/java/com/example/dtf/models/PostWithComments.kt +++ /dev/null @@ -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 -) diff --git a/app/src/main/java/com/example/dtf/models/User.kt b/app/src/main/java/com/example/dtf/models/User.kt index f04ed75..d563c49 100644 --- a/app/src/main/java/com/example/dtf/models/User.kt +++ b/app/src/main/java/com/example/dtf/models/User.kt @@ -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 diff --git a/app/src/main/java/com/example/dtf/repositories/CategoryRepository.kt b/app/src/main/java/com/example/dtf/repositories/CategoryRepository.kt deleted file mode 100644 index 02dac8f..0000000 --- a/app/src/main/java/com/example/dtf/repositories/CategoryRepository.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/CommentRepository.kt b/app/src/main/java/com/example/dtf/repositories/CommentRepository.kt deleted file mode 100644 index 5852b51..0000000 --- a/app/src/main/java/com/example/dtf/repositories/CommentRepository.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/ICategoryRepository.kt b/app/src/main/java/com/example/dtf/repositories/ICategoryRepository.kt new file mode 100644 index 0000000..a80af87 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/ICategoryRepository.kt @@ -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> + fun getAllCached(): Flow> + + fun getFirst(): Flow +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/ICommentRepository.kt b/app/src/main/java/com/example/dtf/repositories/ICommentRepository.kt new file mode 100644 index 0000000..fb4ad0a --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/ICommentRepository.kt @@ -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 + fun getByPost(postId: Int): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/ILikeRepository.kt b/app/src/main/java/com/example/dtf/repositories/ILikeRepository.kt new file mode 100644 index 0000000..af3479d --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/ILikeRepository.kt @@ -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 + fun isLikedByUser(userId: Int, postId: Int): Flow +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/IPostRepository.kt b/app/src/main/java/com/example/dtf/repositories/IPostRepository.kt new file mode 100644 index 0000000..ed06677 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/IPostRepository.kt @@ -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 + fun getByCategory(categoryId: Int): Flow> +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/IUserRepository.kt b/app/src/main/java/com/example/dtf/repositories/IUserRepository.kt new file mode 100644 index 0000000..655ded1 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/IUserRepository.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/LikeRepository.kt b/app/src/main/java/com/example/dtf/repositories/LikeRepository.kt deleted file mode 100644 index 24f107f..0000000 --- a/app/src/main/java/com/example/dtf/repositories/LikeRepository.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/PostRepository.kt b/app/src/main/java/com/example/dtf/repositories/PostRepository.kt deleted file mode 100644 index f12ee02..0000000 --- a/app/src/main/java/com/example/dtf/repositories/PostRepository.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/UserRepository.kt b/app/src/main/java/com/example/dtf/repositories/UserRepository.kt deleted file mode 100644 index 028a762..0000000 --- a/app/src/main/java/com/example/dtf/repositories/UserRepository.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/offline/OfflineCategoryRepository.kt b/app/src/main/java/com/example/dtf/repositories/offline/OfflineCategoryRepository.kt new file mode 100644 index 0000000..0e7dfca --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/offline/OfflineCategoryRepository.kt @@ -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> { + return categoryDao.getAllCached() + } + + override fun getFirst(): Flow { + return categoryDao.getFirst() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/offline/OfflineCommentRepository.kt b/app/src/main/java/com/example/dtf/repositories/offline/OfflineCommentRepository.kt new file mode 100644 index 0000000..a448746 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/offline/OfflineCommentRepository.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/offline/OfflineLikeRepository.kt b/app/src/main/java/com/example/dtf/repositories/offline/OfflineLikeRepository.kt new file mode 100644 index 0000000..e280123 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/offline/OfflineLikeRepository.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/offline/OfflinePostRepository.kt b/app/src/main/java/com/example/dtf/repositories/offline/OfflinePostRepository.kt new file mode 100644 index 0000000..b4e1661 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/offline/OfflinePostRepository.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/offline/OfflineUserRepository.kt b/app/src/main/java/com/example/dtf/repositories/offline/OfflineUserRepository.kt new file mode 100644 index 0000000..1712f9e --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/offline/OfflineUserRepository.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/online/RestCategoryRepository.kt b/app/src/main/java/com/example/dtf/repositories/online/RestCategoryRepository.kt new file mode 100644 index 0000000..8f122a9 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/online/RestCategoryRepository.kt @@ -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> = Pager( + PagingConfig( + pageSize = 3, + enablePlaceholders = false + ), + remoteMediator = CategoryMediator(appDatabase, serverService), + pagingSourceFactory = { appDatabase.categoryDao().getAll() } + ).flow + + override fun getAllCached(): Flow> { + return appDatabase.categoryDao().getAllCached() + } + + override fun getFirst(): Flow { + return appDatabase.categoryDao().getFirst() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/online/RestCommentRepository.kt b/app/src/main/java/com/example/dtf/repositories/online/RestCommentRepository.kt new file mode 100644 index 0000000..ae0549e --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/online/RestCommentRepository.kt @@ -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 { + return flow { emit(serverService.getComment(id)) } + } + + @OptIn(ExperimentalPagingApi::class) + override fun getByPost(postId: Int): Flow> = Pager( + PagingConfig( + pageSize = 5, + enablePlaceholders = false, + ), + remoteMediator = CommentMediator(appDatabase, serverService, postId.toString()), + pagingSourceFactory = { appDatabase.commentDao().getByPost(postId) } + ).flow +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/online/RestLikeRepository.kt b/app/src/main/java/com/example/dtf/repositories/online/RestLikeRepository.kt new file mode 100644 index 0000000..3e66320 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/online/RestLikeRepository.kt @@ -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 { + return flow { emit(serverService.getPostLikes(postId)) } + } + + override fun isLikedByUser(userId: Int, postId: Int): Flow { + return flow { emit(serverService.postIsLiked(postId)) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/online/RestPostRepository.kt b/app/src/main/java/com/example/dtf/repositories/online/RestPostRepository.kt new file mode 100644 index 0000000..87e21c1 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/online/RestPostRepository.kt @@ -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 { + return flow { emit(serverService.getPost(id)) } + } + + @OptIn(ExperimentalPagingApi::class) + override fun getByCategory(categoryId: Int): Flow> = Pager( + PagingConfig( + pageSize = 4, + enablePlaceholders = false + ), + remoteMediator = PostMediator(appDatabase, serverService, categoryId.toString()), + pagingSourceFactory = { appDatabase.postDao().getByCategory(categoryId.toString()) } + ).flow +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/online/RestUserRepository.kt b/app/src/main/java/com/example/dtf/repositories/online/RestUserRepository.kt new file mode 100644 index 0000000..4699adf --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/online/RestUserRepository.kt @@ -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 { + return flow { emit(serverService.getUser(id)) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/online/mediator/CategoryMediator.kt b/app/src/main/java/com/example/dtf/repositories/online/mediator/CategoryMediator.kt new file mode 100644 index 0000000..445bb41 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/online/mediator/CategoryMediator.kt @@ -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(){ + + private val categoryDao = database.categoryDao() + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/online/mediator/CommentMediator.kt b/app/src/main/java/com/example/dtf/repositories/online/mediator/CommentMediator.kt new file mode 100644 index 0000000..8c28105 --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/online/mediator/CommentMediator.kt @@ -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(){ + + private val commentDao = database.commentDao() + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/repositories/online/mediator/PostMediator.kt b/app/src/main/java/com/example/dtf/repositories/online/mediator/PostMediator.kt new file mode 100644 index 0000000..97e166d --- /dev/null +++ b/app/src/main/java/com/example/dtf/repositories/online/mediator/PostMediator.kt @@ -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(){ + + private val postDao = database.postDao() + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/screens/NewPostScreen.kt b/app/src/main/java/com/example/dtf/screens/NewPostScreen.kt index 6db9b89..f8c5bc7 100644 --- a/app/src/main/java/com/example/dtf/screens/NewPostScreen.kt +++ b/app/src/main/java/com/example/dtf/screens/NewPostScreen.kt @@ -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() 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) { diff --git a/app/src/main/java/com/example/dtf/screens/PostScreen.kt b/app/src/main/java/com/example/dtf/screens/PostScreen.kt index e42b0be..18b0e70 100644 --- a/app/src/main/java/com/example/dtf/screens/PostScreen.kt +++ b/app/src/main/java/com/example/dtf/screens/PostScreen.kt @@ -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) ) diff --git a/app/src/main/java/com/example/dtf/screens/PostsScreen.kt b/app/src/main/java/com/example/dtf/screens/PostsScreen.kt index 7866bec..05c8f26 100644 --- a/app/src/main/java/com/example/dtf/screens/PostsScreen.kt +++ b/app/src/main/java/com/example/dtf/screens/PostsScreen.kt @@ -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, posts: MutableState>?>) { - 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} diff --git a/app/src/main/java/com/example/dtf/viewmodels/EditPostViewModel.kt b/app/src/main/java/com/example/dtf/viewmodels/EditPostViewModel.kt index 9b42b6b..7b65e45 100644 --- a/app/src/main/java/com/example/dtf/viewmodels/EditPostViewModel.kt +++ b/app/src/main/java/com/example/dtf/viewmodels/EditPostViewModel.kt @@ -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(null) val editingPostState: LiveData @@ -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 { diff --git a/app/src/main/java/com/example/dtf/viewmodels/LoginViewModel.kt b/app/src/main/java/com/example/dtf/viewmodels/LoginViewModel.kt index 2da272f..9aeb8fa 100644 --- a/app/src/main/java/com/example/dtf/viewmodels/LoginViewModel.kt +++ b/app/src/main/java/com/example/dtf/viewmodels/LoginViewModel.kt @@ -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() val successState: LiveData 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) } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/viewmodels/NewPostViewModel.kt b/app/src/main/java/com/example/dtf/viewmodels/NewPostViewModel.kt index 6ffca76..1cb04a0 100644 --- a/app/src/main/java/com/example/dtf/viewmodels/NewPostViewModel.kt +++ b/app/src/main/java/com/example/dtf/viewmodels/NewPostViewModel.kt @@ -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(null) val addingPostState: LiveData get() = _addingPostState - private val _categories = MutableLiveData>(listOf()) - val categories: LiveData> - 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 ) diff --git a/app/src/main/java/com/example/dtf/viewmodels/PostViewModel.kt b/app/src/main/java/com/example/dtf/viewmodels/PostViewModel.kt index 0ffa1c7..7dd0262 100644 --- a/app/src/main/java/com/example/dtf/viewmodels/PostViewModel.kt +++ b/app/src/main/java/com/example/dtf/viewmodels/PostViewModel.kt @@ -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() val post: LiveData @@ -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> = commentRepository.getByPost(postId) } \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/viewmodels/PostsViewModel.kt b/app/src/main/java/com/example/dtf/viewmodels/PostsViewModel.kt index 70cace9..2839bf6 100644 --- a/app/src/main/java/com/example/dtf/viewmodels/PostsViewModel.kt +++ b/app/src/main/java/com/example/dtf/viewmodels/PostsViewModel.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/viewmodels/ProfileViewModel.kt b/app/src/main/java/com/example/dtf/viewmodels/ProfileViewModel.kt index f1568cc..0b6b2bb 100644 --- a/app/src/main/java/com/example/dtf/viewmodels/ProfileViewModel.kt +++ b/app/src/main/java/com/example/dtf/viewmodels/ProfileViewModel.kt @@ -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() val user: LiveData 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") } } \ No newline at end of file diff --git a/app/src/main/java/com/example/dtf/viewmodels/RegisterViewModel.kt b/app/src/main/java/com/example/dtf/viewmodels/RegisterViewModel.kt index 8bf8b60..4cdfd7a 100644 --- a/app/src/main/java/com/example/dtf/viewmodels/RegisterViewModel.kt +++ b/app/src/main/java/com/example/dtf/viewmodels/RegisterViewModel.kt @@ -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() val successState: LiveData @@ -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) } } } diff --git a/build.gradle b/build.gradle index c4b0614..753531e 100644 --- a/build.gradle +++ b/build.gradle @@ -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 } \ No newline at end of file diff --git a/local.properties b/local.properties index f7299d2..bcd3b42 100644 --- a/local.properties +++ b/local.properties @@ -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