diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostModerationActionHandler.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/PostModerationActionHandler.kt index 92d2a80b..83bc1b42 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostModerationActionHandler.kt +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostModerationActionHandler.kt @@ -3,5 +3,8 @@ package ml.docilealligator.infinityforreddit import ml.docilealligator.infinityforreddit.post.Post interface PostModerationActionHandler { - fun approvePost(post: Post?) + fun approvePost(post: Post, position: Int) + fun removePost(post: Post, position: Int, isSpam: Boolean) + fun toggleSticky(post: Post, position: Int) + fun toggleLock(post: Post, position: Int) } \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java index b266ac25..db0c0a54 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/apis/RedditAPI.java @@ -433,4 +433,20 @@ public interface RedditAPI { @FormUrlEncoded @POST("/api/approve") Call approveThing(@HeaderMap Map headers, @FieldMap Map params); + + @FormUrlEncoded + @POST("/api/remove") + Call removeThing(@HeaderMap Map headers, @FieldMap Map params); + + @FormUrlEncoded + @POST("/api/set_subreddit_sticky") + Call toggleStickyPost(@HeaderMap Map headers, @FieldMap Map params); + + @FormUrlEncoded + @POST("/api/lock") + Call lockThing(@HeaderMap Map headers, @FieldMap Map params); + + @FormUrlEncoded + @POST("/api/unlock") + Call unLockThing(@HeaderMap Map headers, @FieldMap Map params); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/ModerationActionBottomSheetFragment.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/ModerationActionBottomSheetFragment.kt index 9eaad36a..6092215d 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/ModerationActionBottomSheetFragment.kt +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/ModerationActionBottomSheetFragment.kt @@ -4,15 +4,18 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.Fragment import ml.docilealligator.infinityforreddit.PostModerationActionHandler import ml.docilealligator.infinityforreddit.R import ml.docilealligator.infinityforreddit.customviews.LandscapeExpandedRoundedBottomSheetDialogFragment import ml.docilealligator.infinityforreddit.databinding.FragmentModerationActionBottomSheetBinding import ml.docilealligator.infinityforreddit.post.Post +import org.checkerframework.checker.units.qual.A private const val EXTRA_POST = "EP" +private const val EXTRA_POSITION = "EPO" /** * A simple [Fragment] subclass. @@ -21,11 +24,13 @@ private const val EXTRA_POST = "EP" */ class ModerationActionBottomSheetFragment : LandscapeExpandedRoundedBottomSheetDialogFragment() { private var post: Post? = null + private var position: Int = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { post = it.getParcelable(EXTRA_POST) + position = it.getInt(EXTRA_POSITION, -1) } } @@ -33,13 +38,45 @@ class ModerationActionBottomSheetFragment : LandscapeExpandedRoundedBottomSheetD inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - // Inflate the layout for this fragment - val binding: FragmentModerationActionBottomSheetBinding = FragmentModerationActionBottomSheetBinding.inflate(inflater, container, false) - if (parentFragment is PostModerationActionHandler) { - binding.approveTextViewModerationActionBottomSheetFragment.setOnClickListener { - (parentFragment as PostModerationActionHandler).approvePost(post) - dismiss() + post?.let { post -> + if (parentFragment is PostModerationActionHandler) { + binding.approveTextViewModerationActionBottomSheetFragment.setOnClickListener { + (parentFragment as PostModerationActionHandler).approvePost(post, position) + dismiss() + } + + binding.removeTextViewModerationActionBottomSheetFragment.setOnClickListener { + (parentFragment as PostModerationActionHandler).removePost(post, position, false) + dismiss() + } + + binding.spamTextViewModerationActionBottomSheetFragment.setOnClickListener { + (parentFragment as PostModerationActionHandler).removePost(post, position, true) + dismiss() + } + + binding.toggleStickyTextViewModerationActionBottomSheetFragment.setText(if (post.isStickied) R.string.unset_sticky_post else R.string.set_sticky_post) + activity?.let { + binding.toggleStickyTextViewModerationActionBottomSheetFragment.setCompoundDrawablesWithIntrinsicBounds( + AppCompatResources.getDrawable(it, if (post.isStickied) R.drawable.ic_unstick_post_24dp else R.drawable.ic_stick_post_24dp), null, null, null + ) + } + binding.toggleStickyTextViewModerationActionBottomSheetFragment.setOnClickListener { + (parentFragment as PostModerationActionHandler).toggleSticky(post, position) + dismiss() + } + + binding.toggleLockTextViewModerationActionBottomSheetFragment.setText(if (post.isLocked) R.string.unlock else R.string.lock) + activity?.let { + binding.toggleLockTextViewModerationActionBottomSheetFragment.setCompoundDrawablesWithIntrinsicBounds( + AppCompatResources.getDrawable(it, if (post.isLocked) R.drawable.ic_unlock_24dp else R.drawable.ic_lock_day_night_24dp), null, null, null + ) + } + binding.toggleLockTextViewModerationActionBottomSheetFragment.setOnClickListener { + (parentFragment as PostModerationActionHandler).toggleLock(post, position) + dismiss() + } } } return binding.root @@ -47,10 +84,11 @@ class ModerationActionBottomSheetFragment : LandscapeExpandedRoundedBottomSheetD companion object { @JvmStatic - fun newInstance(post: Post) = + fun newInstance(post: Post, position: Int) = ModerationActionBottomSheetFragment().apply { arguments = Bundle().apply { putParcelable(EXTRA_POST, post) + putInt(EXTRA_POSITION, position) } } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/PostOptionsBottomSheetFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/PostOptionsBottomSheetFragment.java index 3a235690..21d62954 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/PostOptionsBottomSheetFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/PostOptionsBottomSheetFragment.java @@ -280,7 +280,7 @@ public class PostOptionsBottomSheetFragment extends LandscapeExpandedRoundedBott if (mPost.isCanModPost()) { binding.modTextViewPostOptionsBottomSheetFragment.setVisibility(View.VISIBLE); binding.modTextViewPostOptionsBottomSheetFragment.setOnClickListener(view -> { - ModerationActionBottomSheetFragment moderationActionBottomSheetFragment = ModerationActionBottomSheetFragment.newInstance(mPost); + ModerationActionBottomSheetFragment moderationActionBottomSheetFragment = ModerationActionBottomSheetFragment.newInstance(mPost, getArguments().getInt(EXTRA_POST_LIST_POSITION, 0)); Fragment parentFragment = getParentFragment(); if (parentFragment != null) { moderationActionBottomSheetFragment.show(parentFragment.getChildFragmentManager(), moderationActionBottomSheetFragment.getTag()); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragment.java index 279d4e5b..932d270f 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragment.java @@ -18,7 +18,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.paging.LoadState; import androidx.recyclerview.widget.RecyclerView; @@ -60,8 +59,8 @@ import ml.docilealligator.infinityforreddit.events.ChangeDefaultPostLayoutEvent; import ml.docilealligator.infinityforreddit.events.ChangeNetworkStatusEvent; import ml.docilealligator.infinityforreddit.events.ChangeSavePostFeedScrolledPositionEvent; import ml.docilealligator.infinityforreddit.events.NeedForPostListFromPostFragmentEvent; +import ml.docilealligator.infinityforreddit.events.PostUpdateEventToPostList; import ml.docilealligator.infinityforreddit.events.ProvidePostListToViewPostDetailActivityEvent; -import ml.docilealligator.infinityforreddit.moderation.ModerationEvent; import ml.docilealligator.infinityforreddit.post.Post; import ml.docilealligator.infinityforreddit.post.PostPagingSource; import ml.docilealligator.infinityforreddit.post.PostViewModel; @@ -881,11 +880,9 @@ public class PostFragment extends PostFragmentBase implements FragmentCommunicat private void bindPostViewModel() { mPostViewModel.getPosts().observe(getViewLifecycleOwner(), posts -> mAdapter.submitData(getViewLifecycleOwner().getLifecycle(), posts)); - mPostViewModel.moderationEventLiveData.observe(getViewLifecycleOwner(), new Observer<>() { - @Override - public void onChanged(ModerationEvent moderationEvent) { - Toast.makeText(activity, R.string.approved, Toast.LENGTH_SHORT).show(); - } + mPostViewModel.moderationEventLiveData.observe(getViewLifecycleOwner(), moderationEvent -> { + EventBus.getDefault().post(new PostUpdateEventToPostList(moderationEvent.getPost(), moderationEvent.getPosition())); + Toast.makeText(activity, moderationEvent.getToastMessageResId(), Toast.LENGTH_SHORT).show(); }); mAdapter.addLoadStateListener(combinedLoadStates -> { @@ -1395,7 +1392,22 @@ public class PostFragment extends PostFragmentBase implements FragmentCommunicat } @Override - public void approvePost(@Nullable Post post) { - mPostViewModel.approvePost(post); + public void approvePost(@NonNull Post post, int position) { + mPostViewModel.approvePost(post, position); + } + + @Override + public void removePost(@NonNull Post post, int position, boolean isSpam) { + mPostViewModel.removePost(post, position, isSpam); + } + + @Override + public void toggleSticky(@NonNull Post post, int position) { + mPostViewModel.toggleStickyPost(post, position); + } + + @Override + public void toggleLock(@NonNull Post post, int position) { + mPostViewModel.toggleLockPost(post, position); } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragmentBase.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragmentBase.java index 6ab9f88d..0d96ae72 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragmentBase.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragmentBase.java @@ -622,6 +622,8 @@ public abstract class PostFragmentBase extends Fragment { post.setSpoiler(event.post.isSpoiler()); post.setFlair(event.post.getFlair()); post.setSaved(event.post.isSaved()); + post.setIsStickied(event.post.isStickied()); + post.setIsLocked(event.post.isLocked()); if (event.post.isRead()) { post.markAsRead(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/moderation/ModerationEvent.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/moderation/ModerationEvent.kt index 0b672b85..a3db51ee 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/moderation/ModerationEvent.kt +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/moderation/ModerationEvent.kt @@ -1,5 +1,27 @@ package ml.docilealligator.infinityforreddit.moderation -enum class ModerationEvent { - APPROVED, APPROVE_FAILED, REMOVED, REMOVE_FAILED, MARKED_AS_SPAM, MARK_AS_SPAM_FAILED +import ml.docilealligator.infinityforreddit.R +import ml.docilealligator.infinityforreddit.post.Post + +sealed class ModerationEvent(open val post: Post, open val position: Int, val toastMessageResId: Int) { + data class Approved(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.approved) + data class ApproveFailed(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.approve_failed) + + data class Removed(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.removed) + data class RemoveFailed(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.remove_failed) + + data class MarkedAsSpam(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.marked_as_spam) + data class MarkAsSpamFailed(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.mark_as_spam_failed) + + data class SetStickyPost(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.set_sticky_post) + data class SetStickyPostFailed(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.set_sticky_post_failed) + + data class UnsetStickyPost(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.unset_sticky_post) + data class UnsetStickyPostFailed(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.unset_sticky_post_failed) + + data class Locked(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.locked) + data class LockFailed(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.lock_failed) + + data class Unlocked(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.unlocked) + data class UnlockFailed(override val post: Post, override val position: Int) : ModerationEvent(post, position, R.string.unlock_failed) } \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/post/Post.java b/app/src/main/java/ml/docilealligator/infinityforreddit/post/Post.java index 6780e628..24aa8498 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/post/Post.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/post/Post.java @@ -62,9 +62,9 @@ public class Post implements Parcelable { private boolean hidden; private boolean spoiler; private boolean nsfw; - private final boolean stickied; + private boolean stickied; private final boolean archived; - private final boolean locked; + private boolean locked; private boolean saved; private final boolean isCrosspost; private boolean isRead; @@ -542,6 +542,10 @@ public class Post implements Parcelable { return stickied; } + public void setIsStickied(boolean value) { + stickied = value; + } + public boolean isArchived() { return archived; } @@ -550,6 +554,10 @@ public class Post implements Parcelable { return locked; } + public void setIsLocked(boolean value) { + locked = value; + } + public boolean isSaved() { return saved; } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/post/PostViewModel.java b/app/src/main/java/ml/docilealligator/infinityforreddit/post/PostViewModel.java index bdef278d..09ebf4a0 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/post/PostViewModel.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/post/PostViewModel.java @@ -494,27 +494,88 @@ public class PostViewModel extends ViewModel { } } - public void approvePost(@Nullable Post post) { - if (post == null) { - moderationEventLiveData.postValue(ModerationEvent.APPROVE_FAILED); - return; - } - + public void approvePost(@NonNull Post post, int position) { Map params = new HashMap<>(); params.put(APIUtils.ID_KEY, post.getFullName()); retrofit.create(RedditAPI.class).approveThing(APIUtils.getOAuthHeader(accessToken), params).enqueue(new Callback<>() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.isSuccessful()) { - moderationEventLiveData.postValue(ModerationEvent.APPROVED); + moderationEventLiveData.postValue(new ModerationEvent.Approved(post, position)); } else { - moderationEventLiveData.postValue(ModerationEvent.APPROVE_FAILED); + moderationEventLiveData.postValue(new ModerationEvent.ApproveFailed(post, position)); } } @Override public void onFailure(@NonNull Call call, @NonNull Throwable throwable) { - moderationEventLiveData.postValue(ModerationEvent.APPROVE_FAILED); + moderationEventLiveData.postValue(new ModerationEvent.ApproveFailed(post, position)); + } + }); + } + + public void removePost(@NonNull Post post, int position, boolean isSpam) { + Map params = new HashMap<>(); + params.put(APIUtils.ID_KEY, post.getFullName()); + params.put(APIUtils.SPAM_KEY, Boolean.toString(isSpam)); + retrofit.create(RedditAPI.class).removeThing(APIUtils.getOAuthHeader(accessToken), params).enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + moderationEventLiveData.postValue(isSpam ? new ModerationEvent.MarkedAsSpam(post, position): new ModerationEvent.Removed(post, position)); + } else { + moderationEventLiveData.postValue(isSpam ? new ModerationEvent.MarkAsSpamFailed(post, position) : new ModerationEvent.RemoveFailed(post, position)); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable throwable) { + moderationEventLiveData.postValue(isSpam ? new ModerationEvent.MarkAsSpamFailed(post, position) : new ModerationEvent.RemoveFailed(post, position)); + } + }); + } + + public void toggleStickyPost(@NonNull Post post, int position) { + Map params = new HashMap<>(); + params.put(APIUtils.ID_KEY, post.getFullName()); + params.put(APIUtils.STATE_KEY, Boolean.toString(!post.isStickied())); + params.put(APIUtils.API_TYPE_KEY, APIUtils.API_TYPE_JSON); + retrofit.create(RedditAPI.class).toggleStickyPost(APIUtils.getOAuthHeader(accessToken), params).enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + post.setIsStickied(!post.isStickied()); + moderationEventLiveData.postValue(post.isStickied() ? new ModerationEvent.SetStickyPost(post, position): new ModerationEvent.UnsetStickyPost(post, position)); + } else { + moderationEventLiveData.postValue(post.isStickied() ? new ModerationEvent.UnsetStickyPostFailed(post, position) : new ModerationEvent.SetStickyPostFailed(post, position)); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable throwable) { + moderationEventLiveData.postValue(post.isStickied() ? new ModerationEvent.UnsetStickyPostFailed(post, position) : new ModerationEvent.SetStickyPostFailed(post, position)); + } + }); + } + + public void toggleLockPost(@NonNull Post post, int position) { + Map params = new HashMap<>(); + params.put(APIUtils.ID_KEY, post.getFullName()); + Call call = post.isLocked() ? retrofit.create(RedditAPI.class).unLockThing(APIUtils.getOAuthHeader(accessToken), params) : retrofit.create(RedditAPI.class).lockThing(APIUtils.getOAuthHeader(accessToken), params); + call.enqueue(new Callback<>() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful()) { + post.setIsLocked(!post.isLocked()); + moderationEventLiveData.postValue(post.isLocked() ? new ModerationEvent.Locked(post, position): new ModerationEvent.Unlocked(post, position)); + } else { + moderationEventLiveData.postValue(post.isLocked() ? new ModerationEvent.UnlockFailed(post, position) : new ModerationEvent.LockFailed(post, position)); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable throwable) { + moderationEventLiveData.postValue(post.isLocked() ? new ModerationEvent.UnlockFailed(post, position) : new ModerationEvent.LockFailed(post, position)); } }); } diff --git a/app/src/main/res/drawable/ic_stick_post_24dp.xml b/app/src/main/res/drawable/ic_stick_post_24dp.xml new file mode 100644 index 00000000..7eefb110 --- /dev/null +++ b/app/src/main/res/drawable/ic_stick_post_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_unlock_24dp.xml b/app/src/main/res/drawable/ic_unlock_24dp.xml new file mode 100644 index 00000000..7755ac61 --- /dev/null +++ b/app/src/main/res/drawable/ic_unlock_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_unstick_post_24dp.xml b/app/src/main/res/drawable/ic_unstick_post_24dp.xml new file mode 100644 index 00000000..039fef38 --- /dev/null +++ b/app/src/main/res/drawable/ic_unstick_post_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_moderation_action_bottom_sheet.xml b/app/src/main/res/layout/fragment_moderation_action_bottom_sheet.xml index 17a39ce6..1eb9fd7e 100644 --- a/app/src/main/res/layout/fragment_moderation_action_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_moderation_action_bottom_sheet.xml @@ -71,6 +71,43 @@ android:textSize="?attr/font_default" app:drawableTint="?attr/primaryTextColor" /> + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52c647a8..4a32195b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1552,5 +1552,18 @@ Approved Approve failed Remove + Removed + Remove failed Mark as spam + Marked as spam + Mark as spam failed + Set sticky post + Set sticky post failed + Unset sticky post + Unset sticky post failed + Lock + Locked + Lock failed + Unlocked + Unlock failed