diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostModerationActionHandler.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/PostModerationActionHandler.kt new file mode 100644 index 00000000..92d2a80b --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostModerationActionHandler.kt @@ -0,0 +1,7 @@ +package ml.docilealligator.infinityforreddit + +import ml.docilealligator.infinityforreddit.post.Post + +interface PostModerationActionHandler { + fun approvePost(post: Post?) +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SingleLiveEvent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SingleLiveEvent.java new file mode 100644 index 00000000..c970787b --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SingleLiveEvent.java @@ -0,0 +1,51 @@ +package ml.docilealligator.infinityforreddit; + +import android.util.Log; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class SingleLiveEvent extends MutableLiveData { + + private static final String TAG = "SingleLiveEvent"; + + private final AtomicBoolean mPending = new AtomicBoolean(false); + + @MainThread + public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer observer) { + + if (hasActiveObservers()) { + Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); + } + + // Observe the internal MutableLiveData + super.observe(owner, new Observer() { + @Override + public void onChanged(@Nullable T t) { + if (mPending.compareAndSet(true, false)) { + observer.onChanged(t); + } + } + }); + } + + @MainThread + public void setValue(@Nullable T t) { + mPending.set(true); + super.setValue(t); + } + + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + public void call() { + setValue(null); + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java index dc84e6d4..0e2e4957 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostDetailRecyclerViewAdapter.java @@ -351,7 +351,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter { if (textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) { CopyTextBottomSheetFragment.show( - mActivity.getSupportFragmentManager(), + mFragment.getChildFragmentManager(), mPost.getSelfTextPlain(), mPost.getSelfText() ); return true; @@ -382,7 +382,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter { CopyTextBottomSheetFragment.show( - mActivity.getSupportFragmentManager(), + mFragment.getChildFragmentManager(), mPost.getSelfTextPlain(), mPost.getSelfText() ); return true; @@ -1630,7 +1630,7 @@ public class PostDetailRecyclerViewAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java index bedf0cd7..5f7bf43a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/PostRecyclerViewAdapter.java @@ -2994,7 +2994,7 @@ public class PostRecyclerViewAdapter extends PagingDataAdapter loadPartialUserData(@Query("ids") String commaSeparatedUserFullNames); + + @FormUrlEncoded + @POST("/api/approve") + Call approveThing(@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 new file mode 100644 index 00000000..9eaad36a --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/ModerationActionBottomSheetFragment.kt @@ -0,0 +1,57 @@ +package ml.docilealligator.infinityforreddit.bottomsheetfragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +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 + + +private const val EXTRA_POST = "EP" + +/** + * A simple [Fragment] subclass. + * Use the [ModerationActionBottomSheetFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class ModerationActionBottomSheetFragment : LandscapeExpandedRoundedBottomSheetDialogFragment() { + private var post: Post? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + post = it.getParcelable(EXTRA_POST) + } + } + + override fun onCreateView( + 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() + } + } + return binding.root + } + + companion object { + @JvmStatic + fun newInstance(post: Post) = + ModerationActionBottomSheetFragment().apply { + arguments = Bundle().apply { + putParcelable(EXTRA_POST, post) + } + } + } +} \ No newline at end of file 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 64bb6bdb..3a235690 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/PostOptionsBottomSheetFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/bottomsheetfragments/PostOptionsBottomSheetFragment.java @@ -176,7 +176,12 @@ public class PostOptionsBottomSheetFragment extends LandscapeExpandedRoundedBott bundle.putParcelable(ShareBottomSheetFragment.EXTRA_POST, mPost); ShareBottomSheetFragment shareBottomSheetFragment = new ShareBottomSheetFragment(); shareBottomSheetFragment.setArguments(bundle); - shareBottomSheetFragment.show(mBaseActivity.getSupportFragmentManager(), shareBottomSheetFragment.getTag()); + Fragment parentFragment = getParentFragment(); + if (parentFragment != null) { + shareBottomSheetFragment.show(parentFragment.getChildFragmentManager(), shareBottomSheetFragment.getTag()); + } else { + shareBottomSheetFragment.show(mBaseActivity.getSupportFragmentManager(), shareBottomSheetFragment.getTag()); + } dismiss(); }); @@ -275,6 +280,13 @@ public class PostOptionsBottomSheetFragment extends LandscapeExpandedRoundedBott if (mPost.isCanModPost()) { binding.modTextViewPostOptionsBottomSheetFragment.setVisibility(View.VISIBLE); binding.modTextViewPostOptionsBottomSheetFragment.setOnClickListener(view -> { + ModerationActionBottomSheetFragment moderationActionBottomSheetFragment = ModerationActionBottomSheetFragment.newInstance(mPost); + Fragment parentFragment = getParentFragment(); + if (parentFragment != null) { + moderationActionBottomSheetFragment.show(parentFragment.getChildFragmentManager(), moderationActionBottomSheetFragment.getTag()); + } else { + moderationActionBottomSheetFragment.show(mBaseActivity.getSupportFragmentManager(), moderationActionBottomSheetFragment.getTag()); + } dismiss(); }); } 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 cea4cec7..279d4e5b 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/PostFragment.java @@ -14,9 +14,11 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +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; @@ -38,6 +40,7 @@ import javax.inject.Provider; import ml.docilealligator.infinityforreddit.FetchPostFilterAndConcatenatedSubredditNames; import ml.docilealligator.infinityforreddit.Infinity; +import ml.docilealligator.infinityforreddit.PostModerationActionHandler; import ml.docilealligator.infinityforreddit.R; import ml.docilealligator.infinityforreddit.RecyclerViewContentScrollingInterface; import ml.docilealligator.infinityforreddit.account.Account; @@ -58,6 +61,7 @@ import ml.docilealligator.infinityforreddit.events.ChangeNetworkStatusEvent; import ml.docilealligator.infinityforreddit.events.ChangeSavePostFeedScrolledPositionEvent; import ml.docilealligator.infinityforreddit.events.NeedForPostListFromPostFragmentEvent; 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; @@ -79,7 +83,7 @@ import retrofit2.Retrofit; /** * A simple {@link PostFragmentBase} subclass. */ -public class PostFragment extends PostFragmentBase implements FragmentCommunicator { +public class PostFragment extends PostFragmentBase implements FragmentCommunicator, PostModerationActionHandler { public static final String EXTRA_NAME = "EN"; public static final String EXTRA_USER_NAME = "EUN"; @@ -877,6 +881,13 @@ 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(); + } + }); + mAdapter.addLoadStateListener(combinedLoadStates -> { LoadState refreshLoadState = combinedLoadStates.getRefresh(); LoadState appendLoadState = combinedLoadStates.getAppend(); @@ -1382,4 +1393,9 @@ public class PostFragment extends PostFragmentBase implements FragmentCommunicat mAdapter.setCanPlayVideo(hasWindowsFocus); } } + + @Override + public void approvePost(@Nullable Post post) { + mPostViewModel.approvePost(post); + } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/moderation/ModerationEvent.kt b/app/src/main/java/ml/docilealligator/infinityforreddit/moderation/ModerationEvent.kt new file mode 100644 index 00000000..0b672b85 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/moderation/ModerationEvent.kt @@ -0,0 +1,5 @@ +package ml.docilealligator.infinityforreddit.moderation + +enum class ModerationEvent { + APPROVED, APPROVE_FAILED, REMOVED, REMOVE_FAILED, MARKED_AS_SPAM, MARK_AS_SPAM_FAILED +} \ No newline at end of file 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 21fca70a..bdef278d 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/post/PostViewModel.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/post/PostViewModel.java @@ -18,13 +18,22 @@ import androidx.paging.PagingData; import androidx.paging.PagingDataTransforms; import androidx.paging.PagingLiveData; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Executor; +import ml.docilealligator.infinityforreddit.SingleLiveEvent; +import ml.docilealligator.infinityforreddit.account.Account; +import ml.docilealligator.infinityforreddit.apis.RedditAPI; +import ml.docilealligator.infinityforreddit.moderation.ModerationEvent; +import ml.docilealligator.infinityforreddit.postfilter.PostFilter; import ml.docilealligator.infinityforreddit.readpost.ReadPostsListInterface; import ml.docilealligator.infinityforreddit.thing.SortType; -import ml.docilealligator.infinityforreddit.account.Account; -import ml.docilealligator.infinityforreddit.postfilter.PostFilter; +import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; import retrofit2.Retrofit; public class PostViewModel extends ViewModel { @@ -51,6 +60,8 @@ public class PostViewModel extends ViewModel { private final MutableLiveData postFilterLiveData; private final SortTypeAndPostFilterLiveData sortTypeAndPostFilterLiveData; + public final SingleLiveEvent moderationEventLiveData = new SingleLiveEvent<>(); + // PostPagingSource.TYPE_FRONT_PAGE public PostViewModel(Executor executor, Retrofit retrofit, @Nullable String accessToken, @NonNull String accountName, SharedPreferences sharedPreferences, SharedPreferences postFeedScrolledPositionSharedPreferences, @@ -482,4 +493,29 @@ public class PostViewModel extends ViewModel { addSource(postFilterLiveData, postFilter -> setValue(Pair.create(postFilter, sortTypeLiveData.getValue()))); } } + + public void approvePost(@Nullable Post post) { + if (post == null) { + moderationEventLiveData.postValue(ModerationEvent.APPROVE_FAILED); + return; + } + + 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); + } else { + moderationEventLiveData.postValue(ModerationEvent.APPROVE_FAILED); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable throwable) { + moderationEventLiveData.postValue(ModerationEvent.APPROVE_FAILED); + } + }); + } } diff --git a/app/src/main/res/drawable/ic_approve_24dp.xml b/app/src/main/res/drawable/ic_approve_24dp.xml new file mode 100644 index 00000000..57a084d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_approve_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_remove_24dp.xml b/app/src/main/res/drawable/ic_remove_24dp.xml new file mode 100644 index 00000000..923c94ea --- /dev/null +++ b/app/src/main/res/drawable/ic_remove_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_spam_24dp.xml b/app/src/main/res/drawable/ic_spam_24dp.xml new file mode 100644 index 00000000..0dac5d2d --- /dev/null +++ b/app/src/main/res/drawable/ic_spam_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 new file mode 100644 index 00000000..17a39ce6 --- /dev/null +++ b/app/src/main/res/layout/fragment_moderation_action_bottom_sheet.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bca1cb90..52c647a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1540,7 +1540,7 @@ HTTP SOCKS Direct - + Jump to Parent Comment Long Press on Post (Non-Media Area) Long Press on Media @@ -1548,4 +1548,9 @@ Preview in Fullscreen Moderation + Approve + Approved + Approve failed + Remove + Mark as spam