This commit is contained in:
erike 2023-08-27 20:00:43 -06:00
parent f91fa2d27d
commit 633b366baf
29 changed files with 253 additions and 100 deletions

View File

@ -7,6 +7,7 @@ import com.isolaatti.auth.data.remote.AuthApi
import com.isolaatti.auth.domain.AuthRepository
import com.isolaatti.connectivity.RetrofitClient
import com.isolaatti.database.AppDatabase
import com.isolaatti.settings.domain.UserIdSetting
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -21,7 +22,7 @@ class Module {
}
@Provides
fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, keyValueDao: KeyValueDao): AuthRepository {
return AuthRepositoryImpl(tokenStorage, authApi, keyValueDao)
fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, userIdSetting: UserIdSetting): AuthRepository {
return AuthRepositoryImpl(tokenStorage, authApi, userIdSetting)
}
}

View File

@ -7,6 +7,7 @@ import com.isolaatti.auth.data.local.TokenStorage
import com.isolaatti.auth.data.remote.AuthApi
import com.isolaatti.auth.data.remote.Credential
import com.isolaatti.auth.domain.AuthRepository
import com.isolaatti.settings.domain.UserIdSetting
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@ -17,11 +18,8 @@ import javax.inject.Inject
class AuthRepositoryImpl @Inject constructor(
private val tokenStorage: TokenStorage,
private val authApi: AuthApi,
private val keyValueDao: KeyValueDao
private val userIdSetting: UserIdSetting
) : AuthRepository {
companion object {
val KEY_USERID = "user_id"
}
override fun authWithEmailAndPassword(
email: String,
password: String
@ -37,7 +35,7 @@ class AuthRepositoryImpl @Inject constructor(
return@flow
}
tokenStorage.storeToken(dto)
keyValueDao.setValue(KeyValueEntity(KEY_USERID, dto.userId.toString()))
userIdSetting.setUserId(dto.userId)
emit(Resource.Success(true))
return@flow
}
@ -66,7 +64,7 @@ class AuthRepositoryImpl @Inject constructor(
}
override fun getUserId(): Flow<Int?> = flow {
emit(keyValueDao.getValue(KEY_USERID).toIntOrNull())
emit(userIdSetting.getUserId())
}
}

View File

@ -25,6 +25,7 @@ import com.isolaatti.picture_viewer.ui.PictureViewerActivity
import com.isolaatti.posting.PostViewerActivity
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
import com.isolaatti.posting.common.domain.Ownable
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
@ -160,7 +161,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
.usePlugin(PicassoImagesPluginDef.picassoImagePlugin)
.usePlugin(LinkifyPlugin.create())
.build()
adapter = PostsRecyclerViewAdapter(markwon, this, null)
adapter = PostsRecyclerViewAdapter(markwon, this)
viewBinding.feedRecyclerView.adapter = adapter
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext())
@ -227,8 +228,8 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
override fun onOptions(postId: Long) {
optionsViewModel.setOptions(Options.myPostOptions, CALLER_ID, postId)
override fun onOptions(post: Ownable) {
optionsViewModel.setOptions(Options.POST_OPTIONS, CALLER_ID, post)
val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
}

View File

@ -2,7 +2,6 @@ package com.isolaatti.home.presentation
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.auth.domain.AuthRepository
import com.isolaatti.posting.posts.domain.PostsRepository
@ -31,12 +30,16 @@ class FeedViewModel @Inject constructor(
if (refresh) {
posts.value = null
}
postsRepository.getFeed(getLastId()).onEach { feedDtoResource ->
when (feedDtoResource) {
postsRepository.getFeed(getLastId()).onEach { listResource ->
when (listResource) {
is Resource.Success -> {
loadingPosts.postValue(false)
posts.postValue(Pair(posts.value?.first?.concatFeed(feedDtoResource.data) ?: feedDtoResource.data, UpdateEvent(UpdateEvent.UpdateType.PAGE_ADDED, null)))
noMoreContent.postValue(feedDtoResource.data?.moreContent == false)
posts.postValue(Pair(postsList?.apply {
addAll(listResource.data ?: listOf())
} ?: listResource.data,
UpdateEvent(UpdateEvent.UpdateType.PAGE_ADDED, null)))
noMoreContent.postValue(listResource.data?.size == 0)
}
is Resource.Loading -> {
@ -45,7 +48,7 @@ class FeedViewModel @Inject constructor(
}
is Resource.Error -> {
errorLoading.postValue(feedDtoResource.errorType)
//errorLoading.postValue(feedDtoResource.errorType)
}
}
isLoadingFromScrolling = false

View File

@ -3,8 +3,8 @@ package com.isolaatti.posting.comments.data.repository
import com.isolaatti.posting.comments.data.remote.CommentDto
import com.isolaatti.posting.comments.data.remote.CommentToPostDto
import com.isolaatti.posting.comments.data.remote.CommentsApi
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
import com.isolaatti.posting.comments.domain.CommentsRepository
import com.isolaatti.posting.comments.domain.model.Comment
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import retrofit2.awaitResponse
@ -12,10 +12,10 @@ import javax.inject.Inject
class CommentsRepositoryImpl @Inject constructor(private val commentsApi: CommentsApi) :
CommentsRepository {
override fun getComments(postId: Long, lastId: Long): Flow<FeedCommentsDto> = flow {
override fun getComments(postId: Long, lastId: Long): Flow<MutableList<Comment>> = flow {
val response = commentsApi.getCommentsOfPosts(postId, lastId, 15).awaitResponse()
if(response.isSuccessful){
response.body()?.let { emit(it) }
response.body()?.let { emit(Comment.fromCommentsDto(it).toMutableList()) }
}
}

View File

@ -2,11 +2,11 @@ package com.isolaatti.posting.comments.domain
import com.isolaatti.posting.comments.data.remote.CommentDto
import com.isolaatti.posting.comments.data.remote.CommentToPostDto
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
import com.isolaatti.posting.comments.domain.model.Comment
import kotlinx.coroutines.flow.Flow
interface CommentsRepository {
fun getComments(postId: Long, lastId: Long): Flow<FeedCommentsDto>
fun getComments(postId: Long, lastId: Long): Flow<MutableList<Comment>>
fun getComment(commentId: Long): Flow<CommentDto>
fun postComment(commentToPostDto: CommentToPostDto, postId: Long): Flow<Boolean>
}

View File

@ -1,12 +1,30 @@
package com.isolaatti.posting.comments.domain.model
import com.isolaatti.posting.comments.data.remote.CommentDto
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
import com.isolaatti.posting.common.domain.Ownable
import com.isolaatti.posting.posts.data.remote.FeedDto
data class Comment(
val id: Long,
val textContent: String,
val userId: Int,
override val userId: Int,
val postId: Long,
val date: String,
val responseForCommentId: Long?,
val linkedDiscussionId: Long?,
val linkedCommentId: Long?
val username: String
) : Ownable {
companion object {
fun fromCommentsDto(dtoList: FeedCommentsDto): List<Comment> {
return dtoList.data.map {
Comment(
id = it.comment.id,
textContent = it.comment.textContent,
userId = it.comment.userId,
postId = it.comment.postId,
date = it.comment.date,
username = it.username
)
}
}
}
}

View File

@ -1,12 +1,11 @@
package com.isolaatti.posting.comments.domain.use_case
import com.isolaatti.posting.comments.data.remote.CommentDto
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
import com.isolaatti.posting.comments.domain.CommentsRepository
import com.isolaatti.posting.comments.domain.model.Comment
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class GetComments @Inject constructor(private val commentsRepository: CommentsRepository) {
operator fun invoke(postId: Long, lastId: Long? = null): Flow<FeedCommentsDto> =
operator fun invoke(postId: Long, lastId: Long? = null): Flow<MutableList<Comment>> =
commentsRepository.getComments(postId, lastId ?: 0)
}

View File

@ -13,6 +13,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.isolaatti.databinding.BottomSheetPostCommentsBinding
import com.isolaatti.posting.common.domain.OnUserInteractedCallback
import com.isolaatti.posting.common.domain.Ownable
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
@ -115,8 +116,8 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
}
override fun onOptions(postId: Long) {
optionsViewModel.setOptions(Options.myCommentOptions, CALLER_ID)
override fun onOptions(comment: Ownable) {
optionsViewModel.setOptions(Options.POST_OPTIONS, CALLER_ID, comment)
val fragment = BottomSheetPostOptionsFragment()
fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG)
}

View File

@ -5,12 +5,13 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.isolaatti.databinding.CommentLayoutBinding
import com.isolaatti.posting.comments.data.remote.CommentDto
import com.isolaatti.posting.comments.domain.model.Comment
import com.isolaatti.posting.common.domain.OnUserInteractedCallback
import com.isolaatti.utils.UrlGen
import com.squareup.picasso.Picasso
import io.noties.markwon.Markwon
class CommentsRecyclerViewAdapter(private var list: List<CommentDto>, private val markwon: Markwon, private val callback: OnUserInteractedCallback) : RecyclerView.Adapter<CommentsRecyclerViewAdapter.CommentViewHolder>() {
class CommentsRecyclerViewAdapter(private var list: List<Comment>, private val markwon: Markwon, private val callback: OnUserInteractedCallback) : RecyclerView.Adapter<CommentsRecyclerViewAdapter.CommentViewHolder>() {
inner class CommentViewHolder(val viewBinding: CommentLayoutBinding) : RecyclerView.ViewHolder(viewBinding.root)
@ -23,21 +24,21 @@ class CommentsRecyclerViewAdapter(private var list: List<CommentDto>, private va
override fun onBindViewHolder(holder: CommentViewHolder, position: Int) {
val comment = list[position]
holder.viewBinding.textViewDate.text = comment.comment.date
markwon.setMarkdown(holder.viewBinding.postContent, comment.comment.textContent)
holder.viewBinding.textViewDate.text = comment.date
markwon.setMarkdown(holder.viewBinding.postContent, comment.textContent)
holder.viewBinding.textViewUsername.text = comment.username
holder.viewBinding.textViewUsername.setOnClickListener {
callback.onProfileClick(comment.comment.userId)
callback.onProfileClick(comment.userId)
}
holder.viewBinding.moreButton.setOnClickListener {
callback.onOptions(comment.comment.id)
callback.onOptions(comment)
}
Picasso.get()
.load(UrlGen.userProfileImage(comment.comment.userId))
.load(UrlGen.userProfileImage(comment.userId))
.into(holder.viewBinding.avatarPicture)
}
fun submitList(commentDtoList: List<CommentDto>) {
fun submitList(commentDtoList: List<Comment>) {
val lastIndex = if(list.count() - 1 < 1) 0 else list.count() - 1
list = commentDtoList
notifyItemRangeChanged(lastIndex, commentDtoList.count())

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.comments.data.remote.CommentDto
import com.isolaatti.posting.comments.domain.model.Comment
import com.isolaatti.posting.comments.domain.use_case.GetComments
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
@ -16,9 +17,9 @@ import javax.inject.Inject
@HiltViewModel
class CommentsViewModel @Inject constructor(private val getComments: GetComments) : ViewModel() {
private val _comments: MutableLiveData<List<CommentDto>> = MutableLiveData()
private val _comments: MutableLiveData<List<Comment>> = MutableLiveData()
val comments: LiveData<List<CommentDto>> get() = _comments
val comments: LiveData<List<Comment>> get() = _comments
/**
* postId to query comments for. First page will be fetched when set.
@ -36,10 +37,10 @@ class CommentsViewModel @Inject constructor(private val getComments: GetComments
viewModelScope.launch {
getComments(postId, lastId).onEach {
val newList = _comments.value?.toMutableList() ?: mutableListOf()
newList.addAll(it.data)
newList.addAll(it)
_comments.postValue(newList)
if(it.data.isNotEmpty()){
lastId = it.data.last().comment.id
if(it.isNotEmpty()){
lastId = it.last().id
}
}.flowOn(Dispatchers.IO).launchIn(this)
@ -49,8 +50,8 @@ class CommentsViewModel @Inject constructor(private val getComments: GetComments
/**
* Use when new comment has been posted
*/
fun putCommentAtTheBeginning(commentDto: CommentDto) {
val newList: MutableList<CommentDto> = mutableListOf(commentDto)
fun putCommentAtTheBeginning(commentDto: Comment) {
val newList: MutableList<Comment> = mutableListOf(commentDto)
newList.addAll(_comments.value ?: mutableListOf())
_comments.postValue(newList)
}

View File

@ -1,7 +1,7 @@
package com.isolaatti.posting.common.domain
interface OnUserInteractedCallback {
fun onOptions(postId: Long)
fun onOptions(postId: Ownable)
fun onProfileClick(userId: Int)
fun onLoadMore()
}

View File

@ -0,0 +1,5 @@
package com.isolaatti.posting.common.domain
interface Ownable {
val userId: Int
}

View File

@ -21,6 +21,8 @@ data class Options(
}
companion object {
const val POST_OPTIONS = 1
val noOptions = Options(0, listOf())
val myPostOptions = Options(R.string.post_options_title, listOf(
Option(R.string.delete, R.drawable.baseline_delete_24, Option.OPTION_DELETE),

View File

@ -3,10 +3,21 @@ package com.isolaatti.posting.common.options_bottom_sheet.presentation
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.common.domain.Ownable
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options.Companion.POST_OPTIONS
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.settings.domain.UserIdSetting
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
class BottomSheetPostOptionsViewModel : ViewModel() {
@HiltViewModel
class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSetting: UserIdSetting) : ViewModel() {
private val _options: MutableLiveData<Options> = MutableLiveData()
val options: LiveData<Options> get() = _options
@ -21,10 +32,28 @@ class BottomSheetPostOptionsViewModel : ViewModel() {
_optionClicked.postValue(null)
}
fun setOptions(options: Options, callerId: Int, payload: Any? = null) {
_options.postValue(options)
fun setOptions(options: Int, callerId: Int, payload: Ownable? = null) {
viewModelScope.launch {
CoroutineScope(Dispatchers.IO).launch {
when(options) {
POST_OPTIONS -> {
userIdSetting.getUserId()?.let { userId ->
if(userId == payload?.userId) {
_options.postValue(Options.myPostOptions)
} else {
_options.postValue(Options.postOptions)
}
_callerId = callerId
_payload = payload
}
}
}
}
}
}

View File

@ -10,18 +10,19 @@ import com.isolaatti.posting.posts.data.remote.FeedsApi
import com.isolaatti.posting.posts.data.remote.PostApi
import com.isolaatti.posting.posts.data.remote.PostDeletedDto
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
override fun getFeed(lastId: Long): Flow<Resource<FeedDto>> = flow {
override fun getFeed(lastId: Long): Flow<Resource<MutableList<Post>>> = flow {
emit(Resource.Loading())
try {
val result = feedsApi.getChronology(lastId, 20).execute()
if(result.isSuccessful) {
emit(Resource.Success(result.body()))
emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) }))
return@flow
}
when(result.code()) {
@ -35,13 +36,13 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
}
}
override fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<FeedDto>> = flow {
override fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>> = flow {
emit(Resource.Loading())
try {
val gson = Gson()
val result = feedsApi.postsOfUser(userId, 20, lastId, olderFirst, gson.toJson(filter)).execute()
if(result.isSuccessful) {
emit(Resource.Success(result.body()))
emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) }))
return@flow
}
when(result.code()) {

View File

@ -5,14 +5,15 @@ import com.isolaatti.posting.posts.data.remote.EditPostDto
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
import com.isolaatti.posting.posts.data.remote.PostDeletedDto
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
interface PostsRepository {
fun getFeed(lastId: Long): Flow<Resource<FeedDto>>
fun getFeed(lastId: Long): Flow<Resource<MutableList<Post>>>
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<FeedDto>>
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>>
fun makePost(createPostDto: CreatePostDto): Flow<Resource<FeedDto.PostDto>>
fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>>

View File

@ -0,0 +1,40 @@
package com.isolaatti.posting.posts.domain.entity
import com.isolaatti.posting.common.domain.Ownable
import com.isolaatti.posting.posts.data.remote.FeedDto
data class Post(
val id: Long,
var textContent: String,
override val userId: Int,
val privacy: Int,
val date: String,
var audioId: String?,
val squadId: String?,
var numberOfLikes: Int,
var numberOfComments: Int,
val userName: String,
val squadName: String?,
var liked: Boolean
) : Ownable {
companion object {
fun fromFeedDto(feedDto: FeedDto): MutableList<Post> {
return feedDto.data.map {
Post(
id = it.post.id,
userId = it.post.userId,
textContent = it.post.textContent,
privacy = it.post.privacy,
date = it.post.date,
audioId = it.post.audioId,
squadId = it.post.squadId,
numberOfComments = it.numberOfComments,
numberOfLikes = it.numberOfLikes,
userName = it.userName,
squadName = it.squadName,
liked = it.liked
)
}.toMutableList()
}
}
}

View File

@ -1,6 +1,7 @@
package com.isolaatti.posting.posts.presentation
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
import com.isolaatti.posting.common.domain.Ownable
abstract class PostListingRecyclerViewAdapterWiring(private val postsViewModelBase: PostListingViewModelBase) : OnUserInteractedWithPostCallback {
@ -16,7 +17,7 @@ abstract class PostListingRecyclerViewAdapterWiring(private val postsViewModelBa
abstract override fun onOpenPost(postId: Long)
abstract override fun onOptions(postId: Long)
abstract override fun onOptions(post: Ownable)
abstract override fun onProfileClick(userId: Int)
}

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.likes.domain.repository.LikesRepository
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.posting.posts.domain.use_case.DeletePost
import com.isolaatti.profile.domain.use_case.GetProfilePosts
import com.isolaatti.utils.Resource
@ -24,8 +25,9 @@ abstract class PostListingViewModelBase : ViewModel() {
@Inject
lateinit var deletePostUseCase: DeletePost
val posts: MutableLiveData<Pair<FeedDto?, UpdateEvent>?> = MutableLiveData()
val posts: MutableLiveData<Pair<MutableList<Post>?, UpdateEvent>?> = MutableLiveData()
val postsList get() = posts.value?.first
val loadingPosts = MutableLiveData(false)
@ -34,7 +36,7 @@ abstract class PostListingViewModelBase : ViewModel() {
val errorLoading: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
var isLoadingFromScrolling = false
fun getLastId(): Long = try { posts.value?.first?.data?.last()?.post?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
fun getLastId(): Long = try { posts.value?.first?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
abstract fun getFeed(refresh: Boolean)
@ -42,12 +44,12 @@ abstract class PostListingViewModelBase : ViewModel() {
fun likePost(postId: Long) {
viewModelScope.launch {
likesRepository.likePost(postId).onEach {likeDto ->
val likedPost = posts.value?.first?.data?.find { post -> post.post.id == likeDto.postId }
val index = posts.value?.first?.data?.indexOf(likedPost)
val likedPost = posts.value?.first?.find { post -> post.id == likeDto.postId }
val index = posts.value?.first?.indexOf(likedPost)
if(index != null){
val temp = posts.value?.first
val temp = posts.value?.first?.toMutableList()
Log.d("***", temp.toString())
temp?.data?.set(index, likedPost!!.apply {
temp?.set(index, likedPost!!.apply {
liked = true
numberOfLikes = likeDto.likesCount
@ -61,10 +63,11 @@ abstract class PostListingViewModelBase : ViewModel() {
fun unLikePost(postId: Long) {
viewModelScope.launch {
likesRepository.unLikePost(postId).onEach {likeDto ->
val likedPost = posts.value?.first?.data?.find { post -> post.post.id == likeDto.postId }
val index = posts.value?.first?.data?.indexOf(likedPost)
val likedPost = posts.value?.first?.find { post -> post.id == likeDto.postId }
val index = posts.value?.first?.indexOf(likedPost)
if(index != null){
posts.value?.first?.data?.set(index, likedPost!!.apply {
val temp = posts.value?.first?.toMutableList()
temp?.set(index, likedPost!!.apply {
liked = false
numberOfLikes = likeDto.likesCount
})
@ -79,11 +82,11 @@ abstract class PostListingViewModelBase : ViewModel() {
deletePostUseCase(postId).onEach { res ->
when(res) {
is Resource.Success -> {
val postDeleted = posts.value?.first?.data?.find { postDto -> postDto.post.id == postId }
val postDeleted = posts.value?.first?.find { post -> post.id == postId }
?: return@onEach
val index = posts.value?.first?.data?.indexOf(postDeleted)
val index = posts.value?.first?.indexOf(postDeleted)
posts.value?.first?.data?.removeAt(index!!)
posts.value?.first?.removeAt(index!!)
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_REMOVED, index)))
}
is Resource.Loading -> {}

View File

@ -12,15 +12,16 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.isolaatti.R
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.utils.UrlGen.userProfileImage
import com.squareup.picasso.Picasso
import io.noties.markwon.Markwon
class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callback: OnUserInteractedWithPostCallback, private var feedDto: FeedDto?) : RecyclerView.Adapter<PostsRecyclerViewAdapter.FeedViewHolder>(){
class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callback: OnUserInteractedWithPostCallback) : RecyclerView.Adapter<PostsRecyclerViewAdapter.FeedViewHolder>(){
private var postList: List<Post>? = null
inner class FeedViewHolder(itemView: View) : ViewHolder(itemView) {
fun bindView(postDto: FeedDto.PostDto, payloads: List<Any>) {
fun bindView(postDto: Post, payloads: List<Any>) {
Log.d("payloads", payloads.count().toString())
val likeButton: MaterialButton = itemView.findViewById(R.id.like_button)
@ -59,17 +60,17 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
val username: TextView = itemView.findViewById(R.id.text_view_username)
username.text = postDto.userName
username.setOnClickListener {
callback.onProfileClick(postDto.post.userId)
callback.onProfileClick(postDto.userId)
}
val profileImageView: ImageView = itemView.findViewById(R.id.avatar_picture)
Picasso.get().load(userProfileImage(postDto.post.userId)).into(profileImageView)
Picasso.get().load(userProfileImage(postDto.userId)).into(profileImageView)
val dateTextView: TextView = itemView.findViewById(R.id.text_view_date)
dateTextView.text = postDto.post.date
dateTextView.text = postDto.date
val content: TextView = itemView.findViewById(R.id.post_content)
markwon.setMarkdown(content, postDto.post.textContent)
markwon.setMarkdown(content, postDto.textContent)
likeButton.isEnabled = true
@ -87,23 +88,23 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
val moreButton: MaterialButton = itemView.findViewById(R.id.more_button)
moreButton.setOnClickListener {
callback.onOptions(postDto.post.id)
callback.onOptions(postDto)
}
likeButton.setOnClickListener {
likeButton.isEnabled = false
if(postDto.liked){
callback.onUnLiked(postDto.post.id)
callback.onUnLiked(postDto.id)
} else {
callback.onLiked(postDto.post.id)
callback.onLiked(postDto.id)
}
}
commentsButton.setOnClickListener {
callback.onComment(postDto.post.id)
callback.onComment(postDto.id)
}
itemView.findViewById<MaterialCardView>(R.id.card).setOnClickListener {
callback.onOpenPost(postDto.post.id)
callback.onOpenPost(postDto.id)
}
}
@ -122,25 +123,25 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
}
var previousSize = 0
override fun getItemCount(): Int = feedDto?.data?.size ?: 0
override fun getItemCount(): Int = postList?.size ?: 0
override fun setHasStableIds(hasStableIds: Boolean) {
super.setHasStableIds(true)
}
@SuppressLint("NotifyDataSetChanged")
fun updateList(updatedFeed: FeedDto, updateEvent: UpdateEvent? = null) {
fun updateList(updatedFeed: List<Post>, updateEvent: UpdateEvent? = null) {
if(updateEvent == null) {
feedDto = updatedFeed
postList = updatedFeed
notifyDataSetChanged()
return
}
val postUpdated = updateEvent.affectedPosition?.let { feedDto?.data?.get(it) }
val postUpdated = updateEvent.affectedPosition?.let { postList?.get(it) }
val position = updateEvent.affectedPosition
previousSize = itemCount
feedDto = updatedFeed
postList = updatedFeed
when(updateEvent.updateType) {
UpdateEvent.UpdateType.POST_LIKED -> {
@ -179,8 +180,8 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
requestedNewContent = false
}
override fun onBindViewHolder(holder: FeedViewHolder, position: Int, payloads: List<Any>) {
holder.bindView(feedDto?.data?.get(position) ?: return, payloads)
val totalItems = feedDto?.data?.size
holder.bindView(postList?.get(position) ?: return, payloads)
val totalItems = postList?.size
if(totalItems != null && totalItems > 0 && !requestedNewContent) {
if(position == totalItems - 1) {
requestedNewContent = true

View File

@ -1,13 +1,13 @@
package com.isolaatti.profile.domain.use_case
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class GetProfilePosts @Inject constructor(private val postsRepository: PostsRepository) {
operator fun invoke(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<FeedDto>> =
operator fun invoke(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>> =
postsRepository.getProfilePosts(userId, lastId, olderFirst, filter)
}

View File

@ -65,8 +65,8 @@ class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetPro
when (feedDtoResource) {
is Resource.Success -> {
loadingPosts.postValue(false)
posts.postValue(Pair(posts.value?.first?.concatFeed(feedDtoResource.data) ?: feedDtoResource.data, UpdateEvent(if(refresh) UpdateEvent.UpdateType.REFRESH else UpdateEvent.UpdateType.PAGE_ADDED, null)))
noMoreContent.postValue(feedDtoResource.data?.moreContent == false)
posts.postValue(Pair(posts.value?.first?.apply { addAll(feedDtoResource.data ?: listOf()) } ?: feedDtoResource.data, UpdateEvent(if(refresh) UpdateEvent.UpdateType.REFRESH else UpdateEvent.UpdateType.PAGE_ADDED, null)))
noMoreContent.postValue(feedDtoResource.data?.size == 0)
}
is Resource.Loading -> {

View File

@ -18,10 +18,12 @@ import com.isolaatti.followers.domain.FollowingState
import com.isolaatti.home.FeedFragment
import com.isolaatti.posting.PostViewerActivity
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
import com.isolaatti.posting.common.domain.Ownable
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.posting.posts.presentation.PostListingRecyclerViewAdapterWiring
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
import com.isolaatti.posting.posts.presentation.UpdateEvent
@ -67,7 +69,7 @@ class ProfileMainFragment : Fragment() {
)
}
private val postsObserver: Observer<Pair<FeedDto?, UpdateEvent>?> = Observer {
private val postsObserver: Observer<Pair<List<Post>?, UpdateEvent>?> = Observer {
if(it?.first != null) {
postsAdapter.updateList(it.first!!, it.second)
postsAdapter.newContentRequestFinished()
@ -188,7 +190,7 @@ class ProfileMainFragment : Fragment() {
.usePlugin(LinkifyPlugin.create())
.build()
postsAdapter = PostsRecyclerViewAdapter(markwon,postListingRecyclerViewAdapterWiring, null )
postsAdapter = PostsRecyclerViewAdapter(markwon,postListingRecyclerViewAdapterWiring )
}
@ -210,8 +212,8 @@ class ProfileMainFragment : Fragment() {
PostViewerActivity.startActivity(requireContext(), postId)
}
override fun onOptions(postId: Long) {
optionsViewModel.setOptions(Options.myPostOptions, FeedFragment.CALLER_ID, postId)
override fun onOptions(post: Ownable) {
optionsViewModel.setOptions(Options.POST_OPTIONS, FeedFragment.CALLER_ID, post)
val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
}

View File

@ -2,6 +2,8 @@ package com.isolaatti.settings
import com.isolaatti.database.AppDatabase
import com.isolaatti.settings.data.KeyValueDao
import com.isolaatti.settings.data.SettingsRepositoryImpl
import com.isolaatti.settings.domain.SettingsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -14,4 +16,9 @@ class Module {
fun provideKeyValueDao(database: AppDatabase): KeyValueDao {
return database.keyValueDao()
}
@Provides
fun provideSettingsRepository(keyValueDao: KeyValueDao): SettingsRepository {
return SettingsRepositoryImpl(keyValueDao)
}
}

View File

@ -8,8 +8,8 @@ import androidx.room.Query
@Dao
interface KeyValueDao {
@Query("SELECT value FROM key_values WHERE id = :key")
fun getValue(key: String): String
suspend fun getValue(key: String): String
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun setValue(entity: KeyValueEntity)
suspend fun setValue(entity: KeyValueEntity)
}

View File

@ -0,0 +1,13 @@
package com.isolaatti.settings.data
import com.isolaatti.settings.domain.SettingsRepository
class SettingsRepositoryImpl(private val keyValueDao: KeyValueDao) : SettingsRepository {
override suspend fun setKeyValue(key: String, value: String) {
keyValueDao.setValue(KeyValueEntity(key, value))
}
override suspend fun getKeyValue(key: String): String? {
return keyValueDao.getValue(key)
}
}

View File

@ -0,0 +1,6 @@
package com.isolaatti.settings.domain
interface SettingsRepository {
suspend fun setKeyValue(key: String, value: String)
suspend fun getKeyValue(key: String): String?
}

View File

@ -0,0 +1,19 @@
package com.isolaatti.settings.domain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
class UserIdSetting @Inject constructor(private val settingsRepository: SettingsRepository) {
suspend fun getUserId(): Int? {
return settingsRepository.getKeyValue(KEY_USER_ID)?.toIntOrNull()
}
suspend fun setUserId(userId: Int) {
settingsRepository.setKeyValue(KEY_USER_ID, userId.toString())
}
companion object {
const val KEY_USER_ID = "userId"
}
}