diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3b4ec64f..2194018b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -75,6 +75,10 @@
android:name=".activities.ProfileActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/account" />
+
. */
+
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.UnderlineSpan;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.core.app.ActivityOptionsCompat;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.preference.PreferenceManager;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.target.CustomTarget;
+import com.bumptech.glide.request.transition.Transition;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import app.fedilab.android.BaseMainActivity;
+import app.fedilab.android.R;
+import app.fedilab.android.client.entities.api.Account;
+import app.fedilab.android.client.entities.api.AdminAccount;
+import app.fedilab.android.client.entities.api.Attachment;
+import app.fedilab.android.databinding.ActivityAdminAccountBinding;
+import app.fedilab.android.helper.Helper;
+import app.fedilab.android.helper.MastodonHelper;
+import app.fedilab.android.helper.SpannableHelper;
+import app.fedilab.android.helper.ThemeHelper;
+import app.fedilab.android.viewmodel.mastodon.AdminVM;
+import app.fedilab.android.viewmodel.mastodon.NodeInfoVM;
+import es.dmoral.toasty.Toasty;
+
+
+public class AdminAccountActivity extends BaseActivity {
+
+ private AdminAccount adminAccount;
+ private Account account;
+ private ScheduledExecutorService scheduledExecutorService;
+ private ActivityAdminAccountBinding binding;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ThemeHelper.applyTheme(this);
+ binding = ActivityAdminAccountBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ setSupportActionBar(binding.toolbar);
+ ActionBar actionBar = getSupportActionBar();
+ Bundle b = getIntent().getExtras();
+ if (b != null) {
+ adminAccount = (AdminAccount) b.getSerializable(Helper.ARG_ACCOUNT);
+ if (adminAccount != null) {
+ account = adminAccount.account;
+ }
+ }
+ postponeEnterTransition();
+
+ //Remove title
+ if (actionBar != null) {
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
+ }
+
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(true);
+ }
+ binding.toolbar.setPopupTheme(Helper.popupStyle());
+ if (account != null) {
+ new Thread(() -> {
+ account = SpannableHelper.convertAccount(AdminAccountActivity.this, account);
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+ Runnable myRunnable = () -> initializeView(account);
+ mainHandler.post(myRunnable);
+
+ }).start();
+
+ } else {
+ Toasty.error(AdminAccountActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+
+ private void initializeView(Account account) {
+ SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(AdminAccountActivity.this);
+ if (account == null) {
+ Toasty.error(AdminAccountActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+ binding.title.setText(String.format(Locale.getDefault(), "@%s", account.acct));
+
+ // MastodonHelper.loadPPMastodon(binding.profilePicture, account);
+ binding.appBar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
+
+ if (Math.abs(verticalOffset) - binding.appBar.getTotalScrollRange() == 0) {
+ binding.profilePicture.setVisibility(View.VISIBLE);
+ binding.title.setVisibility(View.VISIBLE);
+ } else {
+ binding.profilePicture.setVisibility(View.GONE);
+ binding.title.setVisibility(View.GONE);
+ }
+ });
+
+
+ binding.username.setText(String.format(Locale.getDefault(), "@%s", adminAccount.username));
+ binding.domain.setText(adminAccount.domain);
+ binding.email.setText(adminAccount.email);
+ StringBuilder lastActive = new StringBuilder();
+ if (adminAccount.ips != null) {
+ for (AdminAccount.IP ip : adminAccount.ips) {
+ lastActive.append(Helper.shortDateToString(ip.used_at)).append(" - ").append(ip.ip).append("\r\n");
+ }
+ }
+ if (lastActive.toString().trim().length() == 0) {
+ binding.lastActiveContainer.setVisibility(View.GONE);
+ }
+ if (adminAccount.email == null || adminAccount.email.trim().length() == 0) {
+ binding.emailContainer.setVisibility(View.GONE);
+ }
+ binding.lastActive.setText(lastActive.toString());
+ binding.disabled.setText(adminAccount.disabled ? R.string.yes : R.string.no);
+ binding.approved.setText(adminAccount.approved ? R.string.yes : R.string.no);
+ binding.silenced.setText(adminAccount.silenced ? R.string.yes : R.string.no);
+ binding.suspended.setText(adminAccount.suspended ? R.string.yes : R.string.no);
+
+ binding.disableAction.setText(adminAccount.disabled ? R.string.undisable : R.string.disable);
+ binding.approveAction.setText(adminAccount.approved ? R.string.reject : R.string.approve);
+ binding.silenceAction.setText(adminAccount.silenced ? R.string.unsilence : R.string.silence);
+ binding.suspendAction.setText(adminAccount.suspended ? R.string.unsuspend : R.string.suspend);
+
+ AdminVM adminVM = new ViewModelProvider(AdminAccountActivity.this).get(AdminVM.class);
+
+ binding.disableAction.setOnClickListener(v -> {
+ if (adminAccount.disabled) {
+ adminVM.enable(MainActivity.currentInstance, MainActivity.currentToken, account.id)
+ .observe(AdminAccountActivity.this, adminAccountResult -> {
+ adminAccount.disabled = false;
+ binding.disableAction.setText(R.string.disable);
+ binding.disabled.setText(R.string.no);
+ });
+ } else {
+ adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account.id, "disable ", null, null, null, null);
+ adminAccount.disabled = true;
+ binding.disableAction.setText(R.string.undisable);
+ binding.disabled.setText(R.string.yes);
+ }
+ });
+
+ binding.approveAction.setOnClickListener(v -> {
+ if (adminAccount.approved) {
+ adminVM.reject(MainActivity.currentInstance, MainActivity.currentToken, account.id)
+ .observe(AdminAccountActivity.this, adminAccountResult -> {
+ adminAccount.approved = false;
+ binding.approveAction.setText(R.string.approve);
+ binding.approved.setText(R.string.no);
+ });
+ } else {
+ adminVM.approve(MainActivity.currentInstance, MainActivity.currentToken, account.id);
+ adminAccount.approved = true;
+ binding.approveAction.setText(R.string.reject);
+ binding.approved.setText(R.string.yes);
+ }
+ });
+
+ binding.silenceAction.setOnClickListener(v -> {
+ if (adminAccount.disabled) {
+ adminVM.unsilence(MainActivity.currentInstance, MainActivity.currentToken, account.id)
+ .observe(AdminAccountActivity.this, adminAccountResult -> {
+ adminAccount.silenced = false;
+ binding.silenceAction.setText(R.string.silence);
+ binding.disabled.setText(R.string.no);
+ });
+ } else {
+ adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account.id, "silence", null, null, null, null);
+ adminAccount.silenced = true;
+ binding.disableAction.setText(R.string.unsilence);
+ binding.disabled.setText(R.string.yes);
+ }
+ });
+
+ binding.suspendAction.setOnClickListener(v -> {
+ if (adminAccount.disabled) {
+ adminVM.unsuspend(MainActivity.currentInstance, MainActivity.currentToken, account.id)
+ .observe(AdminAccountActivity.this, adminAccountResult -> {
+ adminAccount.suspended = false;
+ binding.suspendAction.setText(R.string.suspend);
+ binding.suspended.setText(R.string.no);
+ });
+ } else {
+ adminVM.performAction(MainActivity.currentInstance, MainActivity.currentToken, account.id, "suspend", null, null, null, null);
+ adminAccount.suspended = true;
+ binding.disableAction.setText(R.string.unsuspend);
+ binding.suspended.setText(R.string.yes);
+ }
+ });
+
+
+ //Retrieve relationship with the connected account
+ List accountListToCheck = new ArrayList<>();
+ accountListToCheck.add(account.id);
+ //Animate emojis
+ if (account.emojis != null && account.emojis.size() > 0) {
+ boolean disableAnimatedEmoji = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
+ if (!disableAnimatedEmoji) {
+ scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+ scheduledExecutorService.scheduleAtFixedRate(() -> binding.accountDn.invalidate(), 0, 130, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ //Tablayout for timelines/following/followers
+
+ boolean disableGif = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_GIF), false);
+ String targetedUrl = disableGif ? account.avatar_static : account.avatar;
+ Glide.with(AdminAccountActivity.this)
+ .asDrawable()
+ .dontTransform()
+ .load(targetedUrl).into(
+ new CustomTarget() {
+ @Override
+ public void onResourceReady(@NonNull final Drawable resource, Transition super Drawable> transition) {
+ binding.profilePicture.setImageDrawable(resource);
+ startPostponedEnterTransition();
+ }
+
+ @Override
+ public void onLoadFailed(@Nullable Drawable errorDrawable) {
+ binding.profilePicture.setImageResource(R.drawable.ic_person);
+ startPostponedEnterTransition();
+ }
+
+ @Override
+ public void onLoadCleared(@Nullable Drawable placeholder) {
+
+ }
+ }
+ );
+ //Load header
+ MastodonHelper.loadProfileMediaMastodon(binding.bannerPp, account, MastodonHelper.MediaAccountType.HEADER);
+ //Redraws icon for locked accounts
+ final float scale = getResources().getDisplayMetrics().density;
+ if (account.locked) {
+ Drawable img = ContextCompat.getDrawable(AdminAccountActivity.this, R.drawable.ic_baseline_lock_24);
+ assert img != null;
+ img.setBounds(0, 0, (int) (16 * scale + 0.5f), (int) (16 * scale + 0.5f));
+ binding.accountUn.setCompoundDrawables(null, null, img, null);
+ } else {
+ binding.accountUn.setCompoundDrawables(null, null, null, null);
+ }
+ //Peertube account watched by a Mastodon account
+ //Bot account
+ if (account.bot) {
+ binding.accountBot.setVisibility(View.VISIBLE);
+ }
+ if (account.acct != null) {
+ setTitle(account.acct);
+ }
+
+
+ final SpannableString content = new SpannableString(getString(R.string.disclaimer_full));
+ content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
+ content.setSpan(new ForegroundColorSpan(ContextCompat.getColor(AdminAccountActivity.this, R.color.cyanea_accent_reference)), 0, content.length(),
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ //This account was moved to another one
+ if (account.moved != null) {
+ binding.accountMoved.setVisibility(View.VISIBLE);
+ Drawable imgTravel = ContextCompat.getDrawable(AdminAccountActivity.this, R.drawable.ic_baseline_card_travel_24);
+ assert imgTravel != null;
+ imgTravel.setBounds(0, 0, (int) (20 * scale + 0.5f), (int) (20 * scale + 0.5f));
+ binding.accountMoved.setCompoundDrawables(imgTravel, null, null, null);
+ //Retrieves content and make account names clickable
+ SpannableString spannableString = SpannableHelper.moveToText(AdminAccountActivity.this, account);
+ binding.accountMoved.setText(spannableString, TextView.BufferType.SPANNABLE);
+ binding.accountMoved.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+
+
+ binding.accountDn.setText(account.span_display_name != null ? account.span_display_name : account.display_name, TextView.BufferType.SPANNABLE);
+ binding.accountUn.setText(String.format("@%s", account.acct));
+ binding.accountUn.setOnLongClickListener(v -> {
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+ String account_id = account.acct;
+ if (account_id.split("@").length == 1)
+ account_id += "@" + BaseMainActivity.currentInstance;
+ ClipData clip = ClipData.newPlainText("mastodon_account_id", "@" + account_id);
+ Toasty.info(AdminAccountActivity.this, getString(R.string.account_id_clipbloard), Toast.LENGTH_SHORT).show();
+ assert clipboard != null;
+ clipboard.setPrimaryClip(clip);
+ return false;
+ });
+
+ MastodonHelper.loadPPMastodon(binding.accountPp, account);
+ binding.accountPp.setOnClickListener(v -> {
+ Intent intent = new Intent(AdminAccountActivity.this, MediaActivity.class);
+ Bundle b = new Bundle();
+ Attachment attachment = new Attachment();
+ attachment.description = account.acct;
+ attachment.preview_url = account.avatar;
+ attachment.url = account.avatar;
+ attachment.remote_url = account.avatar;
+ attachment.type = "image";
+ ArrayList attachments = new ArrayList<>();
+ attachments.add(attachment);
+ b.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments);
+ b.putInt(Helper.ARG_MEDIA_POSITION, 1);
+ intent.putExtras(b);
+ ActivityOptionsCompat options = ActivityOptionsCompat
+ .makeSceneTransitionAnimation(AdminAccountActivity.this, binding.accountPp, attachment.url);
+ // start the new activity
+ startActivity(intent, options.toBundle());
+ });
+
+
+ binding.accountDate.setText(Helper.shortDateToString(account.created_at));
+ binding.accountDate.setVisibility(View.VISIBLE);
+
+ String[] accountInstanceArray = account.acct.split("@");
+ String accountInstance = BaseMainActivity.currentInstance;
+ if (accountInstanceArray.length > 1) {
+ accountInstance = accountInstanceArray[1];
+ }
+
+ NodeInfoVM nodeInfoVM = new ViewModelProvider(AdminAccountActivity.this).get(NodeInfoVM.class);
+ String finalAccountInstance = accountInstance;
+ nodeInfoVM.getNodeInfo(accountInstance).observe(AdminAccountActivity.this, nodeInfo -> {
+ if (nodeInfo != null && nodeInfo.software != null) {
+ binding.instanceInfo.setText(nodeInfo.software.name);
+ binding.instanceInfo.setVisibility(View.VISIBLE);
+
+ binding.instanceInfo.setOnClickListener(v -> {
+ Intent intent = new Intent(AdminAccountActivity.this, InstanceProfileActivity.class);
+ Bundle b = new Bundle();
+ b.putString(Helper.ARG_INSTANCE, finalAccountInstance);
+ intent.putExtras(b);
+ startActivity(intent);
+
+ });
+ }
+ });
+
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int itemId = item.getItemId();
+ if (itemId == android.R.id.home) {
+ finish();
+ return true;
+ }
+ return true;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (scheduledExecutorService != null) {
+ scheduledExecutorService.shutdownNow();
+ scheduledExecutorService = null;
+ }
+ super.onDestroy();
+ }
+
+ public enum action {
+ FOLLOW,
+ UNFOLLOW,
+ BLOCK,
+ UNBLOCK,
+ NOTHING,
+ MUTE,
+ UNMUTE,
+ REPORT,
+ BLOCK_DOMAIN
+ }
+
+
+}
diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java
index 55ed0b51..5d35e711 100644
--- a/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java
+++ b/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java
@@ -15,6 +15,9 @@ package app.fedilab.android.ui.drawer;
* see . */
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -24,6 +27,7 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import java.util.Locale;
+import app.fedilab.android.activities.AdminAccountActivity;
import app.fedilab.android.client.entities.api.AdminAccount;
import app.fedilab.android.databinding.DrawerAdminAccountBinding;
import app.fedilab.android.helper.Helper;
@@ -33,6 +37,7 @@ import app.fedilab.android.helper.MastodonHelper;
public class AdminAccountAdapter extends RecyclerView.Adapter {
private final List adminAccountList;
+ private Context context;
public AdminAccountAdapter(List adminAccountList) {
this.adminAccountList = adminAccountList;
@@ -49,6 +54,7 @@ public class AdminAccountAdapter extends RecyclerView.Adapter {
+ Intent intent = new Intent(context, AdminAccountActivity.class);
+ Bundle b = new Bundle();
+ b.putSerializable(Helper.ARG_ACCOUNT, adminAccount);
+ intent.putExtras(b);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ });
holder.binding.username.setText(adminAccount.account.display_name);
holder.binding.acct.setText(String.format(Locale.getDefault(), "@%s", adminAccount.account.acct));
holder.binding.postCount.setText(String.valueOf(adminAccount.account.statuses_count));
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 a3bb52a3..c73e6021 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
@@ -181,7 +181,7 @@ public class AdminVM extends AndroidViewModel {
String reportId,
String warningPresetId,
String text,
- boolean sendEmailNotification) {
+ Boolean sendEmailNotification) {
MastodonAdminService mastodonAdminService = init(instance);
new Thread(() -> {
Call performActionCall = mastodonAdminService.performAction(token, accountId, type, reportId, warningPresetId, text, sendEmailNotification);
diff --git a/app/src/main/res/layout/activity_admin_account.xml b/app/src/main/res/layout/activity_admin_account.xml
new file mode 100644
index 00000000..664fb531
--- /dev/null
+++ b/app/src/main/res/layout/activity_admin_account.xml
@@ -0,0 +1,495 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/drawer_admin_account.xml b/app/src/main/res/layout/drawer_admin_account.xml
index 95e2072e..87de3994 100644
--- a/app/src/main/res/layout/drawer_admin_account.xml
+++ b/app/src/main/res/layout/drawer_admin_account.xml
@@ -5,6 +5,7 @@
android:layout_marginStart="6dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="6dp"
+ android:id="@+id/admin_account_container"
android:padding="6dp">
Staff
Most recent
Filter
+ Domain
+ Approved
+ Approve