Moderation - Action on accounts

This commit is contained in:
Thomas 2022-05-28 14:46:51 +02:00
parent f04ec65264
commit d0d8974d08
7 changed files with 933 additions and 1 deletions

View file

@ -75,6 +75,10 @@
android:name=".activities.ProfileActivity" android:name=".activities.ProfileActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/account" /> android:label="@string/account" />
<activity
android:name=".activities.AdminAccountActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/account" />
<activity <activity
android:name=".activities.ScheduledActivity" android:name=".activities.ScheduledActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"

View file

@ -0,0 +1,415 @@
package app.fedilab.android.activities;
/* 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.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<String> 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<Drawable>() {
@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<Attachment> 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
}
}

View file

@ -15,6 +15,9 @@ package app.fedilab.android.ui.drawer;
* see <http://www.gnu.org/licenses>. */ * 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.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -24,6 +27,7 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import app.fedilab.android.activities.AdminAccountActivity;
import app.fedilab.android.client.entities.api.AdminAccount; import app.fedilab.android.client.entities.api.AdminAccount;
import app.fedilab.android.databinding.DrawerAdminAccountBinding; import app.fedilab.android.databinding.DrawerAdminAccountBinding;
import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.Helper;
@ -33,6 +37,7 @@ import app.fedilab.android.helper.MastodonHelper;
public class AdminAccountAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class AdminAccountAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<AdminAccount> adminAccountList; private final List<AdminAccount> adminAccountList;
private Context context;
public AdminAccountAdapter(List<AdminAccount> adminAccountList) { public AdminAccountAdapter(List<AdminAccount> adminAccountList) {
this.adminAccountList = adminAccountList; this.adminAccountList = adminAccountList;
@ -49,6 +54,7 @@ public class AdminAccountAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
DrawerAdminAccountBinding itemBinding = DrawerAdminAccountBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); DrawerAdminAccountBinding itemBinding = DrawerAdminAccountBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new AccountAdminViewHolder(itemBinding); return new AccountAdminViewHolder(itemBinding);
} }
@ -58,6 +64,14 @@ public class AdminAccountAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
AdminAccount adminAccount = adminAccountList.get(position); AdminAccount adminAccount = adminAccountList.get(position);
AccountAdminViewHolder holder = (AccountAdminViewHolder) viewHolder; AccountAdminViewHolder holder = (AccountAdminViewHolder) viewHolder;
MastodonHelper.loadPPMastodon(holder.binding.pp, adminAccount.account); MastodonHelper.loadPPMastodon(holder.binding.pp, adminAccount.account);
holder.binding.adminAccountContainer.setOnClickListener(v -> {
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.username.setText(adminAccount.account.display_name);
holder.binding.acct.setText(String.format(Locale.getDefault(), "@%s", adminAccount.account.acct)); holder.binding.acct.setText(String.format(Locale.getDefault(), "@%s", adminAccount.account.acct));
holder.binding.postCount.setText(String.valueOf(adminAccount.account.statuses_count)); holder.binding.postCount.setText(String.valueOf(adminAccount.account.statuses_count));

View file

@ -181,7 +181,7 @@ public class AdminVM extends AndroidViewModel {
String reportId, String reportId,
String warningPresetId, String warningPresetId,
String text, String text,
boolean sendEmailNotification) { Boolean sendEmailNotification) {
MastodonAdminService mastodonAdminService = init(instance); MastodonAdminService mastodonAdminService = init(instance);
new Thread(() -> { new Thread(() -> {
Call<Void> performActionCall = mastodonAdminService.performAction(token, accountId, type, reportId, warningPresetId, text, sendEmailNotification); Call<Void> performActionCall = mastodonAdminService.performAction(token, accountId, type, reportId, warningPresetId, text, sendEmailNotification);

View file

@ -0,0 +1,495 @@
<?xml version="1.0" encoding="utf-8"?><!--
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>.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".activities.ProfileActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="?backgroundColorLight"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="?backgroundColorLight"
android:fitsSystemWindows="true"
app:contentScrim="?colorPrimaryDark"
app:expandedTitleGravity="top"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="?attr/actionBarSize"
app:layout_collapseMode="parallax">
<com.google.android.material.card.MaterialCardView
android:id="@+id/banner_container"
android:layout_width="match_parent"
android:layout_height="@dimen/layout_height_header"
android:layout_margin="6dp"
app:cardElevation="4dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/banner_pp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/profile_banner"
android:scaleType="centerCrop"
android:src="@drawable/default_banner"
tools:src="@tools:sample/backgrounds/scenic" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/avatar_container"
android:layout_width="108dp"
android:layout_height="108dp"
app:cardElevation="6dp"
app:layout_constraintEnd_toEndOf="@id/banner_container"
app:layout_constraintStart_toStartOf="@id/banner_container"
app:layout_constraintTop_toTopOf="@id/banner_container">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/account_pp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="3dp"
android:contentDescription="@string/profile_picture"
android:scaleType="centerCrop"
android:transitionName="@string/activity_porfile_pp"
app:layout_scrollFlags="scroll"
tools:src="@tools:sample/avatars" />
</com.google.android.material.card.MaterialCardView>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/account_dn"
style="@style/TextAppearance.AppCompat.Headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:elevation="5dp"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="@id/banner_container"
app:layout_constraintStart_toStartOf="@id/banner_container"
app:layout_constraintTop_toBottomOf="@id/avatar_container"
tools:text="@tools:sample/first_names" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/account_un"
style="@style/TextAppearance.AppCompat.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:elevation="5dp"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="@id/banner_container"
app:layout_constraintStart_toStartOf="@id/banner_container"
app:layout_constraintTop_toBottomOf="@id/account_dn"
tools:text="\@username\@instance.test" />
<LinearLayout
android:id="@+id/main_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="?actionBarSize"
app:layout_collapseMode="parallax"
app:layout_constraintTop_toBottomOf="@id/account_un">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<LinearLayout
android:id="@+id/names_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:background="@drawable/red_border"
android:text="Peertube"
android:textColor="@color/red_1"
android:visibility="gone"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/account_bot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:background="@drawable/blue_border"
android:text="@string/bot"
android:textColor="@color/mastodonC4"
android:visibility="gone" />
<TextView
android:id="@+id/account_moved"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:drawablePadding="4dp"
android:gravity="center"
android:textSize="16sp"
android:visibility="gone" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:fillViewport="true"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<TextView
android:id="@+id/instance_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginEnd="3dp"
android:background="@drawable/blue_border"
android:singleLine="true"
android:textColor="@color/mastodonC4"
android:visibility="gone" />
<TextView
android:id="@+id/account_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginEnd="3dp"
android:background="@drawable/blue_border"
android:singleLine="true"
android:textColor="@color/mastodonC4"
android:visibility="gone" />
<TextView
android:id="@+id/account_followed_by"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginEnd="3dp"
android:background="@drawable/green_border"
android:singleLine="true"
android:text="@string/followed_by"
android:textColor="@color/verified_text"
android:visibility="gone" />
<TextView
android:id="@+id/account_follow_request"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:layout_marginEnd="3dp"
android:background="@drawable/blue_border"
android:singleLine="true"
android:text="@string/request_sent"
android:textColor="@color/mastodonC4"
android:visibility="gone" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:backgroundTint="?colorPrimaryDark"
app:layout_collapseMode="pin">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/profile_picture"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginEnd="10dp"
android:contentDescription="@string/profile_picture" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
style="@style/TextAppearance.AppCompat.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:ellipsize="end"
android:singleLine="true" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:id="@+id/account_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:text="@string/username" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:text="@string/domain" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/domain"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/email_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:text="@string/email" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/email"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/last_active_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:text="@string/last_active" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/last_active"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:text="@string/approved" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/approved"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/approve_action"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/approve"
android:textColor="@color/cyanea_accent_dark_reference"
app:strokeColor="@color/cyanea_accent_dark_reference" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:text="@string/disabled" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/disabled"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/disable_action"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/disable"
android:textColor="@color/cyanea_accent_dark_reference"
app:strokeColor="@color/cyanea_accent_dark_reference" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:text="@string/silence" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/silenced"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/silence_action"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/silence"
android:textColor="@color/cyanea_accent_dark_reference"
app:strokeColor="@color/cyanea_accent_dark_reference" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:text="@string/suspended" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/suspended"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/suspend_action"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/suspend"
android:textColor="@color/cyanea_accent_dark_reference"
app:strokeColor="@color/cyanea_accent_dark_reference" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -5,6 +5,7 @@
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_marginEnd="6dp" android:layout_marginEnd="6dp"
android:id="@+id/admin_account_container"
android:padding="6dp"> android:padding="6dp">
<androidx.appcompat.widget.LinearLayoutCompat <androidx.appcompat.widget.LinearLayoutCompat

View file

@ -1607,6 +1607,9 @@
<string name="staff">Staff</string> <string name="staff">Staff</string>
<string name="most_recent">Most recent</string> <string name="most_recent">Most recent</string>
<string name="filter">Filter</string> <string name="filter">Filter</string>
<string name="domain">Domain</string>
<string name="approved">Approved</string>
<string name="approve">Approve</string>
</resources> </resources>