Moderation is available in ViewPostDetailFragment.

This commit is contained in:
Docile-Alligator 2025-07-10 23:21:30 -04:00
parent 7fe8443a16
commit 0d76c70034
7 changed files with 379 additions and 7 deletions

View File

@ -1125,12 +1125,15 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter<Recycler
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof PostDetailBaseViewHolder) {
((PostDetailBaseViewHolder) holder).userTextView.setTextColor(mUsernameColor);
((PostDetailBaseViewHolder) holder).userTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
((PostDetailBaseViewHolder) holder).upvoteButton.setIconResource(R.drawable.ic_upvote_24dp);
((PostDetailBaseViewHolder) holder).upvoteButton.setIconTint(ColorStateList.valueOf(mPostIconAndInfoColor));
((PostDetailBaseViewHolder) holder).scoreTextView.setTextColor(mPostIconAndInfoColor);
((PostDetailBaseViewHolder) holder).downvoteButton.setIconResource(R.drawable.ic_downvote_24dp);
((PostDetailBaseViewHolder) holder).downvoteButton.setIconTint(ColorStateList.valueOf(mPostIconAndInfoColor));
((PostDetailBaseViewHolder) holder).flairTextView.setVisibility(View.GONE);
((PostDetailBaseViewHolder) holder).lockedImageView.setVisibility(View.GONE);
((PostDetailBaseViewHolder) holder).spoilerTextView.setVisibility(View.GONE);
((PostDetailBaseViewHolder) holder).nsfwTextView.setVisibility(View.GONE);
((PostDetailBaseViewHolder) holder).contentMarkdownView.setVisibility(View.GONE);

View File

@ -103,6 +103,8 @@ class ModerationActionBottomSheetFragment : LandscapeExpandedRoundedBottomSheetD
(parentFragment as PostModerationActionHandler).toggleMod(post, position)
dismiss()
}
} else {
dismiss()
}
}
return binding.root

View File

@ -1403,12 +1403,12 @@ public class PostFragment extends PostFragmentBase implements FragmentCommunicat
@Override
public void toggleSticky(@NonNull Post post, int position) {
mPostViewModel.toggleStickyPost(post, position);
mPostViewModel.toggleSticky(post, position);
}
@Override
public void toggleLock(@NonNull Post post, int position) {
mPostViewModel.toggleLockPost(post, position);
mPostViewModel.toggleLock(post, position);
}
@Override

View File

@ -39,6 +39,7 @@ import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.ConcatAdapter;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -68,6 +69,7 @@ import javax.inject.Named;
import javax.inject.Provider;
import ml.docilealligator.infinityforreddit.Infinity;
import ml.docilealligator.infinityforreddit.PostModerationActionHandler;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase;
import ml.docilealligator.infinityforreddit.account.Account;
@ -119,12 +121,13 @@ import ml.docilealligator.infinityforreddit.videoautoplay.ExoCreator;
import ml.docilealligator.infinityforreddit.videoautoplay.media.PlaybackInfo;
import ml.docilealligator.infinityforreddit.videoautoplay.media.VolumeInfo;
import ml.docilealligator.infinityforreddit.viewmodels.ViewPostDetailActivityViewModel;
import ml.docilealligator.infinityforreddit.viewmodels.ViewPostDetailFragmentViewModel;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class ViewPostDetailFragment extends Fragment implements FragmentCommunicator {
public class ViewPostDetailFragment extends Fragment implements FragmentCommunicator, PostModerationActionHandler {
public static final String EXTRA_POST_DATA = "EPD";
public static final String EXTRA_POST_ID = "EPI";
@ -234,6 +237,7 @@ public class ViewPostDetailFragment extends Fragment implements FragmentCommunic
private int scrollPosition;
private FragmentViewPostDetailBinding binding;
private RecyclerView mCommentsRecyclerView;
public ViewPostDetailFragmentViewModel viewPostDetailFragmentViewModel;
public ViewPostDetailFragment() {
// Required empty public constructor
@ -565,6 +569,11 @@ public class ViewPostDetailFragment extends Fragment implements FragmentCommunic
postListPosition = getArguments().getInt(EXTRA_POST_LIST_POSITION, -1);
}
viewPostDetailFragmentViewModel = new ViewModelProvider(
this,
ViewPostDetailFragmentViewModel.Companion.provideFactory(mOauthRetrofit, activity.accessToken)
).get(ViewPostDetailFragmentViewModel.class);
bindView();
return binding.getRoot();
@ -648,6 +657,15 @@ public class ViewPostDetailFragment extends Fragment implements FragmentCommunic
VolumeInfo volumeInfo = new VolumeInfo(true, 0f);
return new PlaybackInfo(INDEX_UNSET, TIME_UNSET, volumeInfo);
});
viewPostDetailFragmentViewModel.getModerationEventLiveData().observe(getViewLifecycleOwner(), moderationEvent -> {
mPost = moderationEvent.getPost();
if (mPostAdapter != null) {
mPostAdapter.updatePost(mPost);
}
EventBus.getDefault().post(new PostUpdateEventToPostList(moderationEvent.getPost(), moderationEvent.getPosition()));
Toast.makeText(activity, moderationEvent.getToastMessageResId(), Toast.LENGTH_SHORT).show();
});
}
public void fetchCommentsAfterCommentFilterAvailable() {
@ -1613,7 +1631,9 @@ public class ViewPostDetailFragment extends Fragment implements FragmentCommunic
public void fetchPostSuccess(Post post) {
if (isAdded()) {
mPost = post;
mPostAdapter.updatePost(mPost);
if (mPostAdapter != null) {
mPostAdapter.updatePost(mPost);
}
EventBus.getDefault().post(new PostUpdateEventToPostList(mPost, postListPosition));
setupMenu();
binding.swipeRefreshLayoutViewPostDetailFragment.setRefreshing(false);
@ -2031,4 +2051,39 @@ public class ViewPostDetailFragment extends Fragment implements FragmentCommunic
mPostAdapter.setCanPlayVideo(hasWindowsFocus);
}
}
@Override
public void approvePost(@NonNull Post post, int position) {
viewPostDetailFragmentViewModel.approvePost(post, position);
}
@Override
public void removePost(@NonNull Post post, int position, boolean isSpam) {
viewPostDetailFragmentViewModel.removePost(post, position, isSpam);
}
@Override
public void toggleSticky(@NonNull Post post, int position) {
viewPostDetailFragmentViewModel.toggleSticky(post, position);
}
@Override
public void toggleLock(@NonNull Post post, int position) {
viewPostDetailFragmentViewModel.toggleLock(post, position);
}
@Override
public void toggleNSFW(@NonNull Post post, int position) {
viewPostDetailFragmentViewModel.toggleNSFW(post, position);
}
@Override
public void toggleSpoiler(@NonNull Post post, int position) {
viewPostDetailFragmentViewModel.toggleSpoiler(post, position);
}
@Override
public void toggleMod(@NonNull Post post, int position) {
viewPostDetailFragmentViewModel.toggleMod(post, position);
}
}

View File

@ -535,7 +535,7 @@ public class PostViewModel extends ViewModel {
});
}
public void toggleStickyPost(@NonNull Post post, int position) {
public void toggleSticky(@NonNull Post post, int position) {
Map<String, String> params = new HashMap<>();
params.put(APIUtils.ID_KEY, post.getFullName());
params.put(APIUtils.STATE_KEY, Boolean.toString(!post.isStickied()));
@ -558,7 +558,7 @@ public class PostViewModel extends ViewModel {
});
}
public void toggleLockPost(@NonNull Post post, int position) {
public void toggleLock(@NonNull Post post, int position) {
Map<String, String> params = new HashMap<>();
params.put(APIUtils.ID_KEY, post.getFullName());
Call<String> call = post.isLocked() ? retrofit.create(RedditAPI.class).unLockThing(APIUtils.getOAuthHeader(accessToken), params) : retrofit.create(RedditAPI.class).lockThing(APIUtils.getOAuthHeader(accessToken), params);

View File

@ -0,0 +1,313 @@
package ml.docilealligator.infinityforreddit.viewmodels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import ml.docilealligator.infinityforreddit.SingleLiveEvent
import ml.docilealligator.infinityforreddit.apis.RedditAPI
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.ApproveFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.Approved
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.DistinguishAsModFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.DistinguishedAsMod
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.LockFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.Locked
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.MarkAsSpamFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.MarkNSFWFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.MarkSpoilerFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.MarkedAsSpam
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.MarkedNSFW
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.MarkedSpoiler
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.RemoveFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.SetStickyPost
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.SetStickyPostFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UndistinguishAsModFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UndistinguishedAsMod
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UnlockFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.Unlocked
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UnmarkNSFWFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UnmarkSpoilerFailed
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UnmarkedNSFW
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UnmarkedSpoiler
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UnsetStickyPost
import ml.docilealligator.infinityforreddit.moderation.ModerationEvent.UnsetStickyPostFailed
import ml.docilealligator.infinityforreddit.post.Post
import ml.docilealligator.infinityforreddit.utils.APIUtils
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
class ViewPostDetailFragmentViewModel(
private val oauthRetrofit: Retrofit,
private val accessToken: String?
) : ViewModel() {
val moderationEventLiveData: SingleLiveEvent<ModerationEvent> = SingleLiveEvent()
fun approvePost(post: Post, position: Int) {
val params: MutableMap<String, String> = HashMap()
params[APIUtils.ID_KEY] = post.fullName
oauthRetrofit.create(RedditAPI::class.java)
.approveThing(APIUtils.getOAuthHeader(accessToken), params)
.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
moderationEventLiveData.postValue(Approved(post, position))
} else {
moderationEventLiveData.postValue(ApproveFailed(post, position))
}
}
override fun onFailure(call: Call<String?>, throwable: Throwable) {
moderationEventLiveData.postValue(ApproveFailed(post, position))
}
})
}
fun removePost(post: Post, position: Int, isSpam: Boolean) {
val params: MutableMap<String, String> = HashMap()
params[APIUtils.ID_KEY] = post.fullName
params[APIUtils.SPAM_KEY] = isSpam.toString()
oauthRetrofit.create(RedditAPI::class.java)
.removeThing(APIUtils.getOAuthHeader(accessToken), params)
.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
moderationEventLiveData.postValue(
if (isSpam) MarkedAsSpam(
post,
position
) else ModerationEvent.Removed(post, position)
)
} else {
moderationEventLiveData.postValue(
if (isSpam) MarkAsSpamFailed(
post,
position
) else RemoveFailed(post, position)
)
}
}
override fun onFailure(call: Call<String?>, throwable: Throwable) {
moderationEventLiveData.postValue(
if (isSpam) MarkAsSpamFailed(
post,
position
) else RemoveFailed(post, position)
)
}
})
}
fun toggleSticky(post: Post, position: Int) {
val params: MutableMap<String, String> = HashMap()
params[APIUtils.ID_KEY] = post.fullName
params[APIUtils.STATE_KEY] = (!post.isStickied).toString()
params[APIUtils.API_TYPE_KEY] = APIUtils.API_TYPE_JSON
oauthRetrofit.create(RedditAPI::class.java)
.toggleStickyPost(APIUtils.getOAuthHeader(accessToken), params)
.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
post.setIsStickied(!post.isStickied)
moderationEventLiveData.postValue(
if (post.isStickied) SetStickyPost(
post,
position
) else UnsetStickyPost(post, position)
)
} else {
moderationEventLiveData.postValue(
if (post.isStickied) UnsetStickyPostFailed(
post,
position
) else SetStickyPostFailed(post, position)
)
}
}
override fun onFailure(call: Call<String?>, throwable: Throwable) {
moderationEventLiveData.postValue(
if (post.isStickied) UnsetStickyPostFailed(
post,
position
) else SetStickyPostFailed(post, position)
)
}
})
}
fun toggleLock(post: Post, position: Int) {
val params: MutableMap<String, String> = HashMap()
params[APIUtils.ID_KEY] = post.fullName
val call: Call<String> = if (post.isLocked) oauthRetrofit.create(
RedditAPI::class.java
).unLockThing(APIUtils.getOAuthHeader(accessToken), params) else oauthRetrofit.create(
RedditAPI::class.java
).lockThing(APIUtils.getOAuthHeader(accessToken), params)
call.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
post.setIsLocked(!post.isLocked)
moderationEventLiveData.postValue(
if (post.isLocked) Locked(
post,
position
) else Unlocked(post, position)
)
} else {
moderationEventLiveData.postValue(
if (post.isLocked) UnlockFailed(
post,
position
) else LockFailed(post, position)
)
}
}
override fun onFailure(call: Call<String?>, throwable: Throwable) {
moderationEventLiveData.postValue(
if (post.isLocked) UnlockFailed(
post,
position
) else LockFailed(post, position)
)
}
})
}
fun toggleNSFW(post: Post, position: Int) {
val params: MutableMap<String, String> = HashMap()
params[APIUtils.ID_KEY] = post.fullName
val call: Call<String> = if (post.isNSFW) oauthRetrofit.create(
RedditAPI::class.java
).unmarkNSFW(APIUtils.getOAuthHeader(accessToken), params) else oauthRetrofit.create(
RedditAPI::class.java
).markNSFW(APIUtils.getOAuthHeader(accessToken), params)
call.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
post.isNSFW = !post.isNSFW
moderationEventLiveData.postValue(
if (post.isNSFW) MarkedNSFW(
post,
position
) else UnmarkedNSFW(post, position)
)
} else {
moderationEventLiveData.postValue(
if (post.isNSFW) UnmarkNSFWFailed(
post,
position
) else MarkNSFWFailed(post, position)
)
}
}
override fun onFailure(call: Call<String?>, throwable: Throwable) {
moderationEventLiveData.postValue(
if (post.isNSFW) UnmarkNSFWFailed(
post,
position
) else MarkNSFWFailed(post, position)
)
}
})
}
fun toggleSpoiler(post: Post, position: Int) {
val params: MutableMap<String, String> = HashMap()
params[APIUtils.ID_KEY] = post.fullName
val call: Call<String> = if (post.isSpoiler) oauthRetrofit.create(
RedditAPI::class.java
).unmarkSpoiler(
APIUtils.getOAuthHeader(accessToken),
params
) else oauthRetrofit.create(
RedditAPI::class.java
).markSpoiler(APIUtils.getOAuthHeader(accessToken), params)
call.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
post.isSpoiler = !post.isSpoiler
moderationEventLiveData.postValue(
if (post.isSpoiler) MarkedSpoiler(
post,
position
) else UnmarkedSpoiler(post, position)
)
} else {
moderationEventLiveData.postValue(
if (post.isSpoiler) UnmarkSpoilerFailed(
post,
position
) else MarkSpoilerFailed(post, position)
)
}
}
override fun onFailure(call: Call<String?>, throwable: Throwable) {
moderationEventLiveData.postValue(
if (post.isSpoiler) UnmarkSpoilerFailed(
post,
position
) else MarkSpoilerFailed(post, position)
)
}
})
}
fun toggleMod(post: Post, position: Int) {
val params: MutableMap<String, String> = HashMap()
params[APIUtils.ID_KEY] = post.fullName
params[APIUtils.HOW_KEY] = if (post.isModerator) APIUtils.HOW_NO else APIUtils.HOW_YES
oauthRetrofit.create(RedditAPI::class.java)
.toggleDistinguishedThing(APIUtils.getOAuthHeader(accessToken), params)
.enqueue(object : Callback<String?> {
override fun onResponse(call: Call<String?>, response: Response<String?>) {
if (response.isSuccessful) {
post.setIsModerator(!post.isModerator)
moderationEventLiveData.postValue(
if (post.isModerator) DistinguishedAsMod(
post,
position
) else UndistinguishedAsMod(post, position)
)
} else {
moderationEventLiveData.postValue(
if (post.isModerator) UndistinguishAsModFailed(
post,
position
) else DistinguishAsModFailed(post, position)
)
}
}
override fun onFailure(call: Call<String?>, throwable: Throwable) {
moderationEventLiveData.postValue(
if (post.isModerator) UndistinguishAsModFailed(
post,
position
) else DistinguishAsModFailed(post, position)
)
}
})
}
companion object {
fun provideFactory(oauthRetrofit: Retrofit, accessToken: String?) : ViewModelProvider.Factory {
return object: ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
modelClass: Class<T>,
extras: CreationExtras
): T {
return ViewPostDetailFragmentViewModel(
oauthRetrofit, accessToken
) as T
}
}
}
}
}

View File

@ -85,7 +85,6 @@
android:paddingTop="16dp"
android:paddingEnd="32dp"
android:paddingBottom="16dp"
android:text="@string/mark_as_spam"
android:textColor="?attr/primaryTextColor"
android:textSize="?attr/font_default"
app:drawableTint="?attr/primaryTextColor" />