Domain blocks for admin (create/update)

This commit is contained in:
Thomas 2022-11-25 15:03:14 +01:00
parent 23d2626370
commit e66fbf5fd7
12 changed files with 406 additions and 9 deletions

View File

@ -243,6 +243,11 @@
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/action_about"
android:theme="@style/AppThemeBar" />
<activity
android:name=".activities.admin.AdminDomainBlockActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/blocked_domains"
android:theme="@style/AppThemeBar" />
<activity
android:name=".activities.SuggestionActivity"
android:configChanges="keyboardHidden|orientation|screenSize"

View File

@ -128,7 +128,7 @@ public class AdminActionActivity extends BaseActivity {
@Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
if (canGoBack) {
if (canGoBack && fragmentAdminAccount != null) {
getMenuInflater().inflate(R.menu.menu_admin_account, menu);
}
return super.onCreateOptionsMenu(menu);
@ -297,12 +297,15 @@ public class AdminActionActivity extends BaseActivity {
ThemeHelper.slideViewsToRight(binding.fragmentContainer, binding.buttonContainer, () -> {
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();

View File

@ -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 <http://www.gnu.org/licenses>. */
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<CharSequence> 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;
}
}

View File

@ -188,7 +188,7 @@ public interface MastodonAdminService {
@POST("admin/domain_blocks")
Call<AdminDomainBlock> 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<AdminDomainBlock> 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,

View File

@ -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";

View File

@ -15,6 +15,8 @@ package app.fedilab.android.ui.drawer.admin;
* see <http://www.gnu.org/licenses>. */
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<RecyclerView.ViewHo
holder.binding.date.setText(Helper.shortDateToString(adminDomainBlock.created_at));
holder.binding.title.setText(adminDomainBlock.domain);
holder.binding.severity.setText(adminDomainBlock.severity);
/*holder.binding.mainContainer.setOnClickListener(view -> {
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);
});*/
});
}

View File

@ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.admin;
* see <http://www.gnu.org/licenses>. */
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();
}

View File

@ -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<AdminDomainBlock> createOrUpdateDomainBlock(@NonNull String instance,
String token,
AdminDomainBlock adminDomainBlock) {
MastodonAdminService mastodonAdminService = init(instance);
adminDomainBlockMutableLiveData = new MutableLiveData<>();
new Thread(() -> {
AdminDomainBlock admDomainBlock = null;
Call<AdminDomainBlock> 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<AdminDomainBlock> 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.
*

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 20222 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 <http://www.gnu.org/licenses>.
-->
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/domain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/domain"
android:importantForAutofill="noExcludeDescendants"
android:inputType="text"
android:singleLine="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/admin_domainblock_domain"
android:textSize="12sp" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/severity" />
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/severity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="30dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/admin_domainblock_severity"
android:textSize="12sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/reject_media"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="1"
android:text="@string/admin_reject_media"
app:buttonTint="@color/cyanea_accent_dark_reference" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/admin_domainblock_reject_media"
android:textSize="12sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/reject_reports"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="1"
android:text="@string/admin_reject_reports"
app:buttonTint="@color/cyanea_accent_dark_reference" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/admin_domainblock_reject_reports"
android:textSize="12sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/obfuscate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="1"
android:text="@string/admin_reject_obfuscate"
app:buttonTint="@color/cyanea_accent_dark_reference" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/admin_domainblock_reject_obfuscate"
android:textSize="12sp" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/private_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/private_comment"
android:importantForAutofill="noExcludeDescendants"
android:inputType="text"
android:singleLine="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/admin_domainblock_private_comment"
android:textSize="12sp" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/public_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/public_comment"
android:importantForAutofill="noExcludeDescendants"
android:inputType="text"
android:singleLine="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/admin_domainblock_public_comment"
android:textSize="12sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/save_changes"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="10dp"
android:text="@string/save_changes"
android:textColor="@color/cyanea_accent_dark_reference"
app:icon="@drawable/ic_check_white_24dp"
app:iconTint="@color/cyanea_accent_dark_reference"
app:strokeColor="@color/cyanea_accent_dark_reference" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.core.widget.NestedScrollView>

View File

@ -18,6 +18,7 @@
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="@+id/main_container"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">

View File

@ -17,6 +17,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent">
<!-- Listview status -->
@ -92,4 +93,17 @@
</RelativeLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_margin="@dimen/fab_margin"
android:contentDescription="@string/create_domain_block"
android:src="@drawable/ic_baseline_add_24"
android:visibility="gone"
app:backgroundTint="@color/cyanea_accent_dark_reference"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</RelativeLayout>

View File

@ -354,6 +354,13 @@
<string name="custom_emoji_picker">Custom emoji picker</string>
<string name="favicon">Favicon</string>
<string name="media_description">Add description for media (for the visually impaired)</string>
<string-array name="admin_block_severity">
<item>Silence</item>
<item>Suspend</item>
<item>None</item>
</string-array>
<string-array name="filter_expire">
<item>Never</item>
<item>30 minutes</item>
@ -1932,4 +1939,21 @@
<string name="set_notif_admin_report">New report (moderators)</string>
<string name="open_with_account">Open with another account</string>
<string name="order_lists">Order lists</string>
<string name="reject_media">Reject media</string>
<string name="reject_reports">Reject reports</string>
<string name="admin_domainblock_domain">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.</string>
<string name="severity">Severity</string>
<string name="admin_domainblock_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.</string>
<string name="admin_reject_media">Reject media files</string>
<string name="admin_domainblock_reject_media">Ignore all reports coming from this domain. Irrelevant for suspensions</string>
<string name="admin_reject_reports">Reject reports</string>
<string name="admin_domainblock_reject_reports">Ignore all reports coming from this domain. Irrelevant for suspensions</string>
<string name="admin_reject_obfuscate">Obfuscate domain name</string>
<string name="admin_domainblock_reject_obfuscate">Partially obfuscate the domain name in the list if advertising the list of domain limitations is enabled</string>
<string name="private_comment">Private comment</string>
<string name="admin_domainblock_private_comment">Comment about this domain limitation for internal use by the moderators.</string>
<string name="public_comment">Public comment</string>
<string name="admin_domainblock_public_comment">Comment about this domain limitation for the general public, if advertising the list of domain limitations is enabled.</string>
<string name="saved_changes">Changes have been saved!</string>
<string name="create_domain_block">Create domain block</string>
</resources>