diff --git a/app/src/main/java/com/isolaatti/auth/Module.kt b/app/src/main/java/com/isolaatti/auth/Module.kt index a89f670..80573cb 100644 --- a/app/src/main/java/com/isolaatti/auth/Module.kt +++ b/app/src/main/java/com/isolaatti/auth/Module.kt @@ -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) } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt b/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt index d4fff21..574afff 100644 --- a/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt @@ -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 = flow { - emit(keyValueDao.getValue(KEY_USERID).toIntOrNull()) + emit(userIdSetting.getUserId()) } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/home/FeedFragment.kt b/app/src/main/java/com/isolaatti/home/FeedFragment.kt index be1eeef..b505ec5 100644 --- a/app/src/main/java/com/isolaatti/home/FeedFragment.kt +++ b/app/src/main/java/com/isolaatti/home/FeedFragment.kt @@ -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) } diff --git a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt index 4000715..b3457ba 100644 --- a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt +++ b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt @@ -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 diff --git a/app/src/main/java/com/isolaatti/posting/comments/data/repository/CommentsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/posting/comments/data/repository/CommentsRepositoryImpl.kt index a02d19d..865d664 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/data/repository/CommentsRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/data/repository/CommentsRepositoryImpl.kt @@ -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 = flow { + override fun getComments(postId: Long, lastId: Long): Flow> = 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()) } } } diff --git a/app/src/main/java/com/isolaatti/posting/comments/domain/CommentsRepository.kt b/app/src/main/java/com/isolaatti/posting/comments/domain/CommentsRepository.kt index 894b761..16d1f62 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/domain/CommentsRepository.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/domain/CommentsRepository.kt @@ -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 + fun getComments(postId: Long, lastId: Long): Flow> fun getComment(commentId: Long): Flow fun postComment(commentToPostDto: CommentToPostDto, postId: Long): Flow } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/comments/domain/model/Comment.kt b/app/src/main/java/com/isolaatti/posting/comments/domain/model/Comment.kt index 9f88182..78de5ea 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/domain/model/Comment.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/domain/model/Comment.kt @@ -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 { + 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 + ) + } + } + } +} diff --git a/app/src/main/java/com/isolaatti/posting/comments/domain/use_case/GetComments.kt b/app/src/main/java/com/isolaatti/posting/comments/domain/use_case/GetComments.kt index 4914455..126aa71 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/domain/use_case/GetComments.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/domain/use_case/GetComments.kt @@ -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 = + operator fun invoke(postId: Long, lastId: Long? = null): Flow> = commentsRepository.getComments(postId, lastId ?: 0) } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt b/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt index fd30e4d..cb5fade 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt @@ -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) } diff --git a/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsRecyclerViewAdapter.kt b/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsRecyclerViewAdapter.kt index 8b05895..3efc7b8 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsRecyclerViewAdapter.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsRecyclerViewAdapter.kt @@ -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, private val markwon: Markwon, private val callback: OnUserInteractedCallback) : RecyclerView.Adapter() { +class CommentsRecyclerViewAdapter(private var list: List, private val markwon: Markwon, private val callback: OnUserInteractedCallback) : RecyclerView.Adapter() { inner class CommentViewHolder(val viewBinding: CommentLayoutBinding) : RecyclerView.ViewHolder(viewBinding.root) @@ -23,21 +24,21 @@ class CommentsRecyclerViewAdapter(private var list: List, 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) { + fun submitList(commentDtoList: List) { val lastIndex = if(list.count() - 1 < 1) 0 else list.count() - 1 list = commentDtoList notifyItemRangeChanged(lastIndex, commentDtoList.count()) diff --git a/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsViewModel.kt b/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsViewModel.kt index e63c758..578fe5e 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsViewModel.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/presentation/CommentsViewModel.kt @@ -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> = MutableLiveData() + private val _comments: MutableLiveData> = MutableLiveData() - val comments: LiveData> get() = _comments + val comments: LiveData> 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 = mutableListOf(commentDto) + fun putCommentAtTheBeginning(commentDto: Comment) { + val newList: MutableList = mutableListOf(commentDto) newList.addAll(_comments.value ?: mutableListOf()) _comments.postValue(newList) } diff --git a/app/src/main/java/com/isolaatti/posting/common/domain/OnUserInteractedCallback.kt b/app/src/main/java/com/isolaatti/posting/common/domain/OnUserInteractedCallback.kt index 1f1c1c5..41d36a8 100644 --- a/app/src/main/java/com/isolaatti/posting/common/domain/OnUserInteractedCallback.kt +++ b/app/src/main/java/com/isolaatti/posting/common/domain/OnUserInteractedCallback.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/common/domain/Ownable.kt b/app/src/main/java/com/isolaatti/posting/common/domain/Ownable.kt new file mode 100644 index 0000000..172165b --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/common/domain/Ownable.kt @@ -0,0 +1,5 @@ +package com.isolaatti.posting.common.domain + +interface Ownable { + val userId: Int +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/Options.kt b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/Options.kt index 9a5ad59..04e6a5c 100644 --- a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/Options.kt +++ b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/Options.kt @@ -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), diff --git a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt index ef8d58c..ce3acc5 100644 --- a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt +++ b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt @@ -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 = MutableLiveData() val options: LiveData get() = _options @@ -21,10 +32,28 @@ class BottomSheetPostOptionsViewModel : ViewModel() { _optionClicked.postValue(null) } - fun setOptions(options: Options, callerId: Int, payload: Any? = null) { - _options.postValue(options) - _callerId = callerId - _payload = payload + 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 + } + + } + } + } + + } + + } diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt index 956ca92..24543c5 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt @@ -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> = flow { + override fun getFeed(lastId: Long): Flow>> = 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> = flow { + override fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow>> = 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()) { diff --git a/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt b/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt index 22ed597..966d037 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt @@ -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> + fun getFeed(lastId: Long): Flow>> - fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow> + fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow>> fun makePost(createPostDto: CreatePostDto): Flow> fun editPost(editPostDto: EditPostDto): Flow> diff --git a/app/src/main/java/com/isolaatti/posting/posts/domain/entity/Post.kt b/app/src/main/java/com/isolaatti/posting/posts/domain/entity/Post.kt new file mode 100644 index 0000000..fda9636 --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/domain/entity/Post.kt @@ -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 { + 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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingRecyclerViewAdapterWiring.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingRecyclerViewAdapterWiring.kt index 006513a..60bc6fc 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingRecyclerViewAdapterWiring.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingRecyclerViewAdapterWiring.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt index 5594372..437f0c1 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt @@ -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?> = MutableLiveData() + val posts: MutableLiveData?, UpdateEvent>?> = MutableLiveData() + val postsList get() = posts.value?.first val loadingPosts = MutableLiveData(false) @@ -34,7 +36,7 @@ abstract class PostListingViewModelBase : ViewModel() { val errorLoading: MutableLiveData = 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 -> {} diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt index 99d8f0b..3d4c5f7 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt @@ -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(){ +class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callback: OnUserInteractedWithPostCallback) : RecyclerView.Adapter(){ + private var postList: List? = null inner class FeedViewHolder(itemView: View) : ViewHolder(itemView) { - fun bindView(postDto: FeedDto.PostDto, payloads: List) { + fun bindView(postDto: Post, payloads: List) { 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(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, 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) { - 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 diff --git a/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfilePosts.kt b/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfilePosts.kt index 2b30902..c59c24b 100644 --- a/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfilePosts.kt +++ b/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfilePosts.kt @@ -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> = + operator fun invoke(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow>> = postsRepository.getProfilePosts(userId, lastId, olderFirst, filter) } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt b/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt index dd433c4..907ed31 100644 --- a/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt +++ b/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt @@ -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 -> { diff --git a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt index b6ee56d..1b41ea1 100644 --- a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt +++ b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt @@ -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?> = Observer { + private val postsObserver: Observer?, 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) } diff --git a/app/src/main/java/com/isolaatti/settings/Module.kt b/app/src/main/java/com/isolaatti/settings/Module.kt index 97dd93f..f20e6bf 100644 --- a/app/src/main/java/com/isolaatti/settings/Module.kt +++ b/app/src/main/java/com/isolaatti/settings/Module.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/settings/data/KeyValueDao.kt b/app/src/main/java/com/isolaatti/settings/data/KeyValueDao.kt index 05a77a4..ab59b37 100644 --- a/app/src/main/java/com/isolaatti/settings/data/KeyValueDao.kt +++ b/app/src/main/java/com/isolaatti/settings/data/KeyValueDao.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/settings/data/SettingsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/settings/data/SettingsRepositoryImpl.kt new file mode 100644 index 0000000..722363a --- /dev/null +++ b/app/src/main/java/com/isolaatti/settings/data/SettingsRepositoryImpl.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/settings/domain/SettingsRepository.kt b/app/src/main/java/com/isolaatti/settings/domain/SettingsRepository.kt new file mode 100644 index 0000000..135802e --- /dev/null +++ b/app/src/main/java/com/isolaatti/settings/domain/SettingsRepository.kt @@ -0,0 +1,6 @@ +package com.isolaatti.settings.domain + +interface SettingsRepository { + suspend fun setKeyValue(key: String, value: String) + suspend fun getKeyValue(key: String): String? +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/settings/domain/UserIdSetting.kt b/app/src/main/java/com/isolaatti/settings/domain/UserIdSetting.kt new file mode 100644 index 0000000..a87cfe9 --- /dev/null +++ b/app/src/main/java/com/isolaatti/settings/domain/UserIdSetting.kt @@ -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" + } +} \ No newline at end of file