diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderPostActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderPostActions.java index 573fd989c137..4a5ecdf86133 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderPostActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderPostActions.java @@ -35,6 +35,10 @@ import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResultListener; import org.wordpress.android.ui.reader.models.ReaderSimplePost; import org.wordpress.android.ui.reader.models.ReaderSimplePostList; +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent; +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeFailure; +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeSuccess; +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeUnChanged; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.DateTimeUtils; @@ -67,6 +71,10 @@ public static boolean performLikeAction(final ReaderPost post, boolean isCurrentlyLiked = ReaderPostTable.isPostLikedByCurrentUser(post); if (isCurrentlyLiked == isAskingToLike) { AppLog.w(T.READER, "post like unchanged"); + final PostLikeUnChanged onPostLikeUnChanged = + new ReaderRepositoryEvent.PostLikeEnded.PostLikeUnChanged( + post.postId, post.blogId, isAskingToLike, wpComUserId); + EventBus.getDefault().post(onPostLikeUnChanged); return false; } @@ -91,6 +99,10 @@ public static boolean performLikeAction(final ReaderPost post, @Override public void onResponse(JSONObject jsonObject) { AppLog.d(T.READER, String.format("post %s succeeded", actionName)); + final PostLikeSuccess onPostLikeSuccess = + new ReaderRepositoryEvent.PostLikeEnded.PostLikeSuccess( + post.postId, post.blogId, isAskingToLike, wpComUserId); + EventBus.getDefault().post(onPostLikeSuccess); } }; @@ -106,6 +118,10 @@ public void onErrorResponse(VolleyError volleyError) { AppLog.e(T.READER, volleyError); ReaderPostTable.setLikesForPost(post, post.numLikes, post.isLikedByCurrentUser); ReaderLikeTable.setCurrentUserLikesPost(post, post.isLikedByCurrentUser, wpComUserId); + final PostLikeFailure onPostLikeFailure = + new ReaderRepositoryEvent.PostLikeEnded.PostLikeFailure( + post.postId, post.blogId, isAskingToLike, wpComUserId); + EventBus.getDefault().post(onPostLikeFailure); } }; @@ -113,6 +129,7 @@ public void onErrorResponse(VolleyError volleyError) { return true; } + /* * get the latest version of this post - note that the post is only considered changed if the * like/comment count has changed, or if the current user's like/follow status has changed diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverRepository.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverRepository.kt index 3a3182fa8d70..58e9cb3ac33b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverRepository.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverRepository.kt @@ -5,17 +5,23 @@ import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.wordpress.android.models.ReaderPost import org.wordpress.android.models.ReaderPostList import org.wordpress.android.models.ReaderTag import org.wordpress.android.models.ReaderTagType.DEFAULT import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.ui.reader.ReaderConstants import org.wordpress.android.ui.reader.ReaderEvents.UpdatePostsEnded +import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Failure import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Success +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeFailure +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeSuccess +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeUnChanged import org.wordpress.android.ui.reader.repository.usecases.FetchPostsForTagUseCase import org.wordpress.android.ui.reader.repository.usecases.GetNumPostsForTagUseCase import org.wordpress.android.ui.reader.repository.usecases.GetPostsForTagUseCase import org.wordpress.android.ui.reader.repository.usecases.GetPostsForTagWithCountUseCase +import org.wordpress.android.ui.reader.repository.usecases.PostLikeActionUseCase import org.wordpress.android.ui.reader.repository.usecases.ShouldAutoUpdateTagUseCase import org.wordpress.android.ui.reader.utils.ReaderUtilsWrapper import org.wordpress.android.viewmodel.Event @@ -32,7 +38,8 @@ class ReaderDiscoverRepository constructor( private val shouldAutoUpdateTagUseCase: ShouldAutoUpdateTagUseCase, private val getPostsForTagWithCountUseCase: GetPostsForTagWithCountUseCase, private val fetchPostsForTagUseCase: FetchPostsForTagUseCase, - private val readerUpdatePostsEndedHandler: ReaderUpdatePostsEndedHandler + private val readerUpdatePostsEndedHandler: ReaderUpdatePostsEndedHandler, + private val postLikeActionUseCase: PostLikeActionUseCase ) : CoroutineScope { override val coroutineContext: CoroutineContext get() = bgDispatcher @@ -63,6 +70,23 @@ class ReaderDiscoverRepository constructor( shouldAutoUpdateTagUseCase.stop() getPostsForTagWithCountUseCase.stop() readerUpdatePostsEndedHandler.stop() + postLikeActionUseCase.stop() + } + + // todo - can change this to blogId, feedId, etc + fun performLikeAction(post: ReaderPost, isAskingToLike: Boolean, wpComUserId: Long) { + launch { + when (val event = postLikeActionUseCase.perform(post, isAskingToLike, wpComUserId)) { + is PostLikeSuccess -> { + reloadPosts() + } + is PostLikeFailure -> { + _communicationChannel.postValue(Event(Failure(event))) + reloadPosts() + } + is PostLikeUnChanged -> { } + } + } } fun getTag(): ReaderTag { @@ -126,7 +150,8 @@ class ReaderDiscoverRepository constructor( private val shouldAutoUpdateTagUseCase: ShouldAutoUpdateTagUseCase, private val getPostsForTagWithCountUseCase: GetPostsForTagWithCountUseCase, private val fetchPostsForTagUseCase: FetchPostsForTagUseCase, - private val readerUpdatePostsEndedHandler: ReaderUpdatePostsEndedHandler + private val readerUpdatePostsEndedHandler: ReaderUpdatePostsEndedHandler, + private val postLikeActionUseCase: PostLikeActionUseCase ) { fun create(readerTag: ReaderTag? = null): ReaderDiscoverRepository { val tag = readerTag @@ -140,7 +165,8 @@ class ReaderDiscoverRepository constructor( shouldAutoUpdateTagUseCase, getPostsForTagWithCountUseCase, fetchPostsForTagUseCase, - readerUpdatePostsEndedHandler + readerUpdatePostsEndedHandler, + postLikeActionUseCase ) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt index ba783f3fb355..7791ab850c6c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt @@ -5,15 +5,21 @@ import androidx.lifecycle.MutableLiveData import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.wordpress.android.models.ReaderPost import org.wordpress.android.models.ReaderPostList import org.wordpress.android.models.ReaderTag import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.ui.reader.ReaderEvents.UpdatePostsEnded +import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Failure import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Success +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeFailure +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeSuccess +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded.PostLikeUnChanged import org.wordpress.android.ui.reader.repository.usecases.FetchPostsForTagUseCase import org.wordpress.android.ui.reader.repository.usecases.GetNumPostsForTagUseCase import org.wordpress.android.ui.reader.repository.usecases.GetPostsForTagUseCase import org.wordpress.android.ui.reader.repository.usecases.GetPostsForTagWithCountUseCase +import org.wordpress.android.ui.reader.repository.usecases.PostLikeActionUseCase import org.wordpress.android.ui.reader.repository.usecases.ShouldAutoUpdateTagUseCase import org.wordpress.android.viewmodel.Event import org.wordpress.android.viewmodel.ReactiveMutableLiveData @@ -29,16 +35,18 @@ class ReaderPostRepository( private val shouldAutoUpdateTagUseCase: ShouldAutoUpdateTagUseCase, private val getPostsForTagWithCountUseCase: GetPostsForTagWithCountUseCase, private val fetchPostsForTagUseCase: FetchPostsForTagUseCase, - private val readerUpdatePostsEndedHandler: ReaderUpdatePostsEndedHandler + private val readerUpdatePostsEndedHandler: ReaderUpdatePostsEndedHandler, + private val postLikeActionUseCase: PostLikeActionUseCase ) : CoroutineScope { override val coroutineContext: CoroutineContext get() = bgDispatcher private var isStarted = false + private var isDirty = false - private val _postsForTag = ReactiveMutableLiveData( - onActive = { onActivePostsForTag() }, onInactive = { onInactivePostsForTag() }) - val postsForTag: LiveData = _postsForTag + private val _posts = ReactiveMutableLiveData( + onActive = { onActivePosts() }, onInactive = { onInactivePosts() }) + val posts: LiveData = _posts private val _communicationChannel = MutableLiveData>() val communicationChannel: LiveData> = _communicationChannel @@ -47,11 +55,13 @@ class ReaderPostRepository( if (isStarted) return isStarted = true - readerUpdatePostsEndedHandler.start(readerTag, + readerUpdatePostsEndedHandler.start( + readerTag, ReaderUpdatePostsEndedHandler.setUpdatePostsEndedListeners( this::onNewPosts, this::onChangedPosts, this::onUnchanged, this::onFailed - )) + ) + ) } fun stop() { @@ -66,6 +76,24 @@ class ReaderPostRepository( return readerTag } + // todo: annmarie - Possibly implement a "LikeManager" that will encapsulate all the "UseCase". + fun performLikeAction(post: ReaderPost, isAskingToLike: Boolean, wpComUserId: Long) { + launch { + when (val event = postLikeActionUseCase.perform(post, isAskingToLike, wpComUserId)) { + is PostLikeSuccess -> { + reloadPosts() + } + is PostLikeFailure -> { + _communicationChannel.postValue(Event(Failure(event))) + reloadPosts() + } + is PostLikeUnChanged -> { + // Unused + } + } + } + } + private fun onNewPosts(event: UpdatePostsEnded) { reloadPosts() } @@ -79,26 +107,27 @@ class ReaderPostRepository( private fun onFailed(event: UpdatePostsEnded) { _communicationChannel.postValue( - Event(ReaderRepositoryCommunication.Error.RemoteRequestFailure)) + Event(ReaderRepositoryCommunication.Error.RemoteRequestFailure) + ) } - private fun onActivePostsForTag() { + private fun onActivePosts() { loadPosts() } - private fun onInactivePostsForTag() { + private fun onInactivePosts() { } private fun loadPosts() { launch { - val existsInMemory = postsForTag.value?.let { + val existsInMemory = posts.value?.let { !it.isEmpty() } ?: false val refresh = shouldAutoUpdateTagUseCase.get(readerTag) if (!existsInMemory) { val result = getPostsForTagUseCase.get(readerTag) - _postsForTag.postValue(result) + _posts.postValue(result) } if (refresh) { @@ -111,7 +140,7 @@ class ReaderPostRepository( private fun reloadPosts() { launch { val result = getPostsForTagUseCase.get(readerTag) - _postsForTag.postValue(result) + _posts.postValue(result) } } @@ -123,7 +152,8 @@ class ReaderPostRepository( private val shouldAutoUpdateTagUseCase: ShouldAutoUpdateTagUseCase, private val getPostsForTagWithCountUseCase: GetPostsForTagWithCountUseCase, private val fetchPostsForTagUseCase: FetchPostsForTagUseCase, - private val readerUpdatePostsEndedHandler: ReaderUpdatePostsEndedHandler + private val readerUpdatePostsEndedHandler: ReaderUpdatePostsEndedHandler, + private val postLikeActionUseCase: PostLikeActionUseCase ) { fun create(readerTag: ReaderTag): ReaderPostRepository { return ReaderPostRepository( @@ -134,7 +164,9 @@ class ReaderPostRepository( shouldAutoUpdateTagUseCase, getPostsForTagWithCountUseCase, fetchPostsForTagUseCase, - readerUpdatePostsEndedHandler + readerUpdatePostsEndedHandler, + postLikeActionUseCase + ) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderRepositoryCommunication.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderRepositoryCommunication.kt deleted file mode 100644 index eeb78c5fce31..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderRepositoryCommunication.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.wordpress.android.ui.reader.repository - -sealed class ReaderRepositoryCommunication { - object Success : ReaderRepositoryCommunication() - sealed class Error : ReaderRepositoryCommunication() { - object NetworkUnavailable : Error() - object RemoteRequestFailure : Error() - class ReaderRepositoryException(val exception: Exception) : Error() - } - - override fun toString(): String { - return "${this.javaClass.simpleName})" - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderRepositoryEvents.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderRepositoryEvents.kt new file mode 100644 index 000000000000..b27a4f6988e0 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderRepositoryEvents.kt @@ -0,0 +1,31 @@ +package org.wordpress.android.ui.reader.repository + +sealed class ReaderRepositoryEvent { + sealed class PostLikeEnded( + val postId: Long, + val blogId: Long, + val isAskingToLike: Boolean, + val wpComUserId: Long + ) : ReaderRepositoryEvent() { + class PostLikeSuccess(postId: Long, blogId: Long, isAskingToLike: Boolean, wpComUserId: Long) : + PostLikeEnded(postId, blogId, isAskingToLike, wpComUserId) + class PostLikeFailure(postId: Long, blogId: Long, isAskingToLike: Boolean, wpComUserId: Long) : + PostLikeEnded(postId, blogId, isAskingToLike, wpComUserId) + class PostLikeUnChanged(postId: Long, blogId: Long, isAskingToLike: Boolean, wpComUserId: Long) : + PostLikeEnded(postId, blogId, isAskingToLike, wpComUserId) + } +} + +sealed class ReaderRepositoryCommunication { + object Success : ReaderRepositoryCommunication() + class Failure(val event: ReaderRepositoryEvent) : ReaderRepositoryCommunication() + sealed class Error : ReaderRepositoryCommunication() { + object NetworkUnavailable : Error() + object RemoteRequestFailure : Error() + class ReaderRepositoryException(val exception: Exception) : Error() + } + + override fun toString(): String { + return "${this.javaClass.simpleName})" + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetPostsForTagUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetPostsForTagUseCase.kt index fccbfb1d7bf2..7fd9461aaa4b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetPostsForTagUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetPostsForTagUseCase.kt @@ -12,12 +12,16 @@ import javax.inject.Named class GetPostsForTagUseCase @Inject constructor( @Named(IO_THREAD) private val ioDispatcher: CoroutineDispatcher ) : ReaderRepositoryDispatchingUseCase(ioDispatcher) { - suspend fun get(readerTag: ReaderTag): ReaderPostList = + suspend fun get( + readerTag: ReaderTag, + maxRows: Int = 0, + excludeTextColumns: Boolean = true + ): ReaderPostList = withContext(coroutineContext) { ReaderPostTable.getPostsWithTag( readerTag, - MAX_ROWS, - EXCLUDE_TEXT_COLUMN + maxRows, + excludeTextColumns ) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetPostsForTagWithCountUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetPostsForTagWithCountUseCase.kt index 96d9ac965f7d..4938746ab5e8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetPostsForTagWithCountUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/GetPostsForTagWithCountUseCase.kt @@ -13,13 +13,17 @@ import javax.inject.Named class GetPostsForTagWithCountUseCase @Inject constructor( @Named(IO_THREAD) private val ioDispatcher: CoroutineDispatcher ) : ReaderRepositoryDispatchingUseCase(ioDispatcher) { - suspend fun get(readerTag: ReaderTag): Pair = + suspend fun get( + readerTag: ReaderTag, + maxRows: Int = 0, + excludeTextColumns: Boolean = true + ): Pair = withContext(coroutineContext) { val postsForTagFromLocalDeferred = async { ReaderPostTable.getPostsWithTag( readerTag, - MAX_ROWS, - EXCLUDE_TEXT_COLUMN + maxRows, + excludeTextColumns ) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/PostLikeActionUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/PostLikeActionUseCase.kt new file mode 100644 index 000000000000..e48e1ac030f6 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/PostLikeActionUseCase.kt @@ -0,0 +1,67 @@ +package org.wordpress.android.ui.reader.repository.usecases + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.suspendCancellableCoroutine +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode.BACKGROUND +import org.wordpress.android.models.ReaderPost +import org.wordpress.android.modules.IO_THREAD +import org.wordpress.android.ui.reader.actions.ReaderPostActions +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent +import org.wordpress.android.ui.reader.repository.ReaderRepositoryEvent.PostLikeEnded +import org.wordpress.android.util.EventBusWrapper +import javax.inject.Inject +import javax.inject.Named +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +class PostLikeActionUseCase @Inject constructor( + @Named(IO_THREAD) private val ioDispatcher: CoroutineDispatcher, + private val eventBusWrapper: EventBusWrapper +) : ReaderRepositoryDispatchingUseCase(ioDispatcher) { + private val continuations: MutableMap?> = mutableMapOf() + + init { + eventBusWrapper.register(this) + } + + suspend fun perform( + post: ReaderPost, + isAskingToLike: Boolean, + wpComUserId: Long + ): ReaderRepositoryEvent { + val request = PostLikeRequest(post.postId, post.blogId, isAskingToLike, wpComUserId) + + if (continuations[request] != null) { + return PostLikeEnded.PostLikeUnChanged(post.postId, post.blogId, isAskingToLike, wpComUserId) + } + + return suspendCancellableCoroutine { cont -> + continuations[request] = cont + ReaderPostActions.performLikeAction(post, isAskingToLike, wpComUserId) + } + } + + @Subscribe(threadMode = BACKGROUND) + @SuppressWarnings("unused") + fun onPerformPostLikeEnded(event: ReaderRepositoryEvent) { + if (event is PostLikeEnded) { + val request = PostLikeRequest(event.postId, event.blogId, event.isAskingToLike, event.wpComUserId) + continuations[request]?.resume(event) // this just ends the method, passing the event back to the caller + continuations[request] = null + } + } + + override fun stop() { + super.stop() + eventBusWrapper.unregister(this) + continuations.run { clear() } + } + + data class PostLikeRequest( + val postId: Long, + val blogId: Long, + val isAskingToLike: Boolean, + val wpComUserId: Long + ) +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/ReaderRepositoryDispatchingUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/ReaderRepositoryDispatchingUseCase.kt index 26c424c81109..ab592f5f2898 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/ReaderRepositoryDispatchingUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/ReaderRepositoryDispatchingUseCase.kt @@ -4,8 +4,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.cancelChildren -import org.wordpress.android.ui.reader.ReaderConstants import org.wordpress.android.util.AppLog import org.wordpress.android.util.AppLog.T import kotlin.coroutines.CoroutineContext @@ -24,13 +22,8 @@ abstract class ReaderRepositoryDispatchingUseCase( throwable.printStackTrace() } - fun stop() { - parentJob.cancelChildren() - } - - companion object { - const val EXCLUDE_TEXT_COLUMN = true - const val MAX_ROWS = ReaderConstants.READER_MAX_POSTS_TO_DISPLAY + open fun stop() { + parentJob.cancel() } } // todo: annmarie - this should be removed - it's a helper log fun for coroutines