From e66fbf5fd75323b206863cfc07d09450bac49a89 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 15:03:14 +0100 Subject: [PATCH] Domain blocks for admin (create/update) --- app/src/main/AndroidManifest.xml | 5 + .../activities/admin/AdminActionActivity.java | 5 +- .../admin/AdminDomainBlockActivity.java | 123 +++++++++++++ .../endpoints/MastodonAdminService.java | 6 +- .../app/fedilab/android/helper/Helper.java | 2 + .../ui/drawer/admin/AdminDomainAdapter.java | 21 ++- .../fragment/admin/FragmentAdminDomain.java | 9 + .../android/viewmodel/mastodon/AdminVM.java | 43 +++++ .../res/layout/activity_admin_domainblock.xml | 162 ++++++++++++++++++ .../main/res/layout/drawer_admin_domain.xml | 1 + .../main/res/layout/fragment_pagination.xml | 14 ++ app/src/main/res/values/strings.xml | 24 +++ 12 files changed, 406 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java create mode 100644 app/src/main/res/layout/activity_admin_domainblock.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fb92343..be259dfd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -243,6 +243,11 @@ android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/action_about" android:theme="@style/AppThemeBar" /> + { if (fragmentAdminReport != null) { fragmentAdminReport.onDestroyView(); + fragmentAdminReport = null; } if (fragmentAdminAccount != null) { fragmentAdminAccount.onDestroyView(); + fragmentAdminAccount = null; } if (fragmentAdminDomain != null) { fragmentAdminDomain.onDestroyView(); + fragmentAdminDomain = null; } setTitle(R.string.administration); invalidateOptionsMenu(); diff --git a/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java b/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java new file mode 100644 index 00000000..e7d53d80 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java @@ -0,0 +1,123 @@ +package app.fedilab.android.activities.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; + +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; + +import app.fedilab.android.R; +import app.fedilab.android.activities.BaseActivity; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; +import app.fedilab.android.databinding.ActivityAdminDomainblockBinding; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.viewmodel.mastodon.AdminVM; +import es.dmoral.toasty.Toasty; + +public class AdminDomainBlockActivity extends BaseActivity { + + + private final String[] severityChoices = {"silence", "suspend", "noop"}; + private AdminVM adminVM; + private AdminDomainBlock adminDomainBlock; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ThemeHelper.applyThemeBar(this); + ActivityAdminDomainblockBinding binding = ActivityAdminDomainblockBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); + } + Bundle b = getIntent().getExtras(); + if (b != null) { + adminDomainBlock = (AdminDomainBlock) b.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK); + } + + ArrayAdapter adapterResize = ArrayAdapter.createFromResource(this, + R.array.admin_block_severity, android.R.layout.simple_spinner_dropdown_item); + binding.severity.setAdapter(adapterResize); + + if (adminDomainBlock != null) { + binding.domain.setText(adminDomainBlock.domain); + binding.domain.setEnabled(false); + for (int i = 0; i < severityChoices.length; i++) { + if (adminDomainBlock.severity.equalsIgnoreCase(severityChoices[i])) { + binding.severity.setSelection(i, false); + break; + } + } + binding.obfuscate.setChecked(adminDomainBlock.obfuscate); + binding.rejectMedia.setChecked(adminDomainBlock.reject_media); + binding.rejectReports.setChecked(adminDomainBlock.reject_reports); + binding.privateComment.setText(adminDomainBlock.private_comment); + binding.publicComment.setText(adminDomainBlock.public_comment); + } else { + adminDomainBlock = new AdminDomainBlock(); + } + + binding.severity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long l) { + adminDomainBlock.severity = severityChoices[position]; + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + } + }); + binding.obfuscate.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.obfuscate = checked); + binding.rejectMedia.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.reject_media = checked); + binding.rejectReports.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.reject_reports = checked); + adminVM = new ViewModelProvider(AdminDomainBlockActivity.this).get(AdminVM.class); + binding.saveChanges.setOnClickListener(v -> { + adminDomainBlock.domain = binding.domain.getText().toString().trim(); + adminDomainBlock.public_comment = binding.publicComment.getText().toString().trim(); + adminDomainBlock.private_comment = binding.privateComment.getText().toString().trim(); + adminVM.createOrUpdateDomainBlock(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock) + .observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> { + if (adminDomainBlockResult != null) { + Toasty.success(AdminDomainBlockActivity.this, getString(R.string.saved_changes), Toasty.LENGTH_SHORT).show(); + } else { + Toasty.error(AdminDomainBlockActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); + } + + } + ); + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + finish(); + return true; + } + return true; + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java index ee57e8ec..eeacfde6 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java @@ -188,7 +188,7 @@ public interface MastodonAdminService { @POST("admin/domain_blocks") Call blockDomain( @Header("Authorization") String app_token, - @Path("domain") String domain, + @Field("domain") String domain, @Field("severity") String severity, @Field("reject_media") Boolean reject_media, @Field("reject_reports") Boolean reject_reports, @@ -205,10 +205,10 @@ public interface MastodonAdminService { ); @FormUrlEncoded - @PUT("admin/domain_blocks") + @PUT("admin/domain_blocks/{id}") Call updateBlockDomain( @Header("Authorization") String app_token, - @Path("domain") String domain, + @Path("id") String id, @Field("severity") String severity, @Field("reject_media") Boolean reject_media, @Field("reject_reports") Boolean reject_reports, diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index f533c3ab..14d19388 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -238,6 +238,8 @@ public class Helper { public static final String ARG_STATUS_REPLY_ID = "ARG_STATUS_REPLY_ID"; public static final String ARG_ACCOUNT = "ARG_ACCOUNT"; public static final String ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID"; + public static final String ARG_ADMIN_DOMAINBLOCK = "ARG_ADMIN_DOMAINBLOCK"; + public static final String ARG_REPORT = "ARG_REPORT"; public static final String ARG_ACCOUNT_MENTION = "ARG_ACCOUNT_MENTION"; public static final String ARG_MINIFIED = "ARG_MINIFIED"; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java index 502a2488..bee4ccee 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java @@ -15,6 +15,8 @@ package app.fedilab.android.ui.drawer.admin; * see . */ import android.content.Context; +import android.content.Intent; +import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -25,6 +27,8 @@ import org.jetbrains.annotations.NotNull; import java.util.List; +import app.fedilab.android.R; +import app.fedilab.android.activities.admin.AdminDomainBlockActivity; import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; import app.fedilab.android.databinding.DrawerAdminDomainBinding; import app.fedilab.android.helper.Helper; @@ -55,14 +59,21 @@ public class AdminDomainAdapter extends RecyclerView.Adapter { - Intent intent = new Intent(context, AccountReportActivity.class); + String text = adminDomainBlock.severity; + if (adminDomainBlock.reject_media) { + text += " - " + context.getString(R.string.reject_media); + } + if (adminDomainBlock.reject_reports) { + text += " - " + context.getString(R.string.reject_reports); + } + holder.binding.severity.setText(text); + holder.binding.mainContainer.setOnClickListener(view -> { + Intent intent = new Intent(context, AdminDomainBlockActivity.class); Bundle b = new Bundle(); - b.putSerializable(Helper.ARG_REPORT, report); + b.putSerializable(Helper.ARG_ADMIN_DOMAINBLOCK, adminDomainBlock); intent.putExtras(b); context.startActivity(intent); - });*/ + }); } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java index f146c0fb..d5f78812 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java @@ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.admin; * see . */ +import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -33,6 +34,7 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; +import app.fedilab.android.activities.admin.AdminDomainBlockActivity; import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; import app.fedilab.android.client.entities.api.admin.AdminDomainBlocks; import app.fedilab.android.databinding.FragmentPaginationBinding; @@ -84,6 +86,13 @@ public class FragmentAdminDomain extends Fragment { adminVM.getDomainBlocks( BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); + binding.addAction.setVisibility(View.VISIBLE); + binding.addAction.setOnClickListener(v -> { + Intent intent = new Intent(requireActivity(), AdminDomainBlockActivity.class); + Bundle b = new Bundle(); + intent.putExtras(b); + startActivity(intent); + }); return binding.getRoot(); } diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java index 9f3d17b3..c45fe41c 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java @@ -17,6 +17,7 @@ package app.fedilab.android.viewmodel.mastodon; import android.app.Application; import android.os.Handler; import android.os.Looper; +import android.util.Log; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; @@ -632,6 +633,48 @@ public class AdminVM extends AndroidViewModel { } + /** + * View a single blocked domain + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @return {@link LiveData} containing a {@link List} of {@link AdminDomainBlocks}s + */ + public LiveData createOrUpdateDomainBlock(@NonNull String instance, + String token, + AdminDomainBlock adminDomainBlock) { + MastodonAdminService mastodonAdminService = init(instance); + adminDomainBlockMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + AdminDomainBlock admDomainBlock = null; + Call getDomainBlock; + if (adminDomainBlock.id == null) { + getDomainBlock = mastodonAdminService.blockDomain(token, adminDomainBlock.domain, adminDomainBlock.severity, adminDomainBlock.reject_media, adminDomainBlock.reject_reports, adminDomainBlock.private_comment, adminDomainBlock.public_comment, adminDomainBlock.obfuscate); + } else { + getDomainBlock = mastodonAdminService.updateBlockDomain(token, adminDomainBlock.id, adminDomainBlock.severity, adminDomainBlock.reject_media, adminDomainBlock.reject_reports, adminDomainBlock.private_comment, adminDomainBlock.public_comment, adminDomainBlock.obfuscate); + } + if (getDomainBlock != null) { + try { + Response getDomainBlocksResponse = getDomainBlock.execute(); + if (getDomainBlocksResponse.isSuccessful()) { + admDomainBlock = getDomainBlocksResponse.body(); + } else { + Log.v(Helper.TAG, "errr: " + getDomainBlocksResponse.errorBody().string()); + } + } catch (Exception e) { + Log.v(Helper.TAG, "e: " + e.getMessage()); + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + AdminDomainBlock finalAdminDomainBlock = admDomainBlock; + Runnable myRunnable = () -> adminDomainBlockMutableLiveData.setValue(finalAdminDomainBlock); + mainHandler.post(myRunnable); + }).start(); + return adminDomainBlockMutableLiveData; + } + + /** * View all allowed domains. * diff --git a/app/src/main/res/layout/activity_admin_domainblock.xml b/app/src/main/res/layout/activity_admin_domainblock.xml new file mode 100644 index 00000000..6e1e1caf --- /dev/null +++ b/app/src/main/res/layout/activity_admin_domainblock.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/drawer_admin_domain.xml b/app/src/main/res/layout/drawer_admin_domain.xml index 57ea5204..63d5dbec 100644 --- a/app/src/main/res/layout/drawer_admin_domain.xml +++ b/app/src/main/res/layout/drawer_admin_domain.xml @@ -18,6 +18,7 @@ diff --git a/app/src/main/res/layout/fragment_pagination.xml b/app/src/main/res/layout/fragment_pagination.xml index 2cf598f4..f26f23ce 100644 --- a/app/src/main/res/layout/fragment_pagination.xml +++ b/app/src/main/res/layout/fragment_pagination.xml @@ -17,6 +17,7 @@ @@ -92,4 +93,17 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d07413c..8741ba6a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -354,6 +354,13 @@ Custom emoji picker Favicon Add description for media (for the visually impaired) + + + Silence + Suspend + None + + Never 30 minutes @@ -1932,4 +1939,21 @@ New report (moderators) Open with another account Order lists + Reject media + Reject reports + The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts. + Severity + Silence will make the account\'s posts invisible to anyone who isn\'t following them. Suspend will remove all of the account\'s content, media, and profile data. Use None if you just want to reject media files. + Reject media files + Ignore all reports coming from this domain. Irrelevant for suspensions + Reject reports + Ignore all reports coming from this domain. Irrelevant for suspensions + Obfuscate domain name + Partially obfuscate the domain name in the list if advertising the list of domain limitations is enabled + Private comment + Comment about this domain limitation for internal use by the moderators. + Public comment + Comment about this domain limitation for the general public, if advertising the list of domain limitations is enabled. + Changes have been saved! + Create domain block \ No newline at end of file