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 8fb92343f..be259dfde 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 000000000..e7d53d800 --- /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 ee57e8ec1..eeacfde60 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 f533c3ab6..14d193887 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 502a2488f..bee4ccee2 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 f146c0fb8..d5f78812c 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 9f3d17b31..c45fe41ca 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 000000000..6e1e1caf1 --- /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 57ea5204c..63d5dbec5 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 2cf598f44..f26f23cef 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 4d07413c1..8741ba6a6 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