per-account color palette preference
This commit is contained in:
@@ -32,7 +32,6 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
UiUtils.setUserPreferredTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
if(savedInstanceState==null){
|
||||
|
||||
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
||||
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
|
||||
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
|
||||
|
||||
@@ -57,7 +57,6 @@ public class GlobalUserPreferences{
|
||||
public static boolean allowRemoteLoading;
|
||||
public static boolean forwardReportDefault;
|
||||
public static AutoRevealMode autoRevealEqualSpoilers;
|
||||
public static ColorPreference color;
|
||||
public static boolean disableM3PillActiveIndicator;
|
||||
public static boolean showNavigationLabels;
|
||||
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
|
||||
@@ -133,14 +132,8 @@ public class GlobalUserPreferences{
|
||||
.apply();
|
||||
}
|
||||
|
||||
try {
|
||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
||||
} catch (IllegalArgumentException|ClassCastException ignored) {
|
||||
// invalid color name or color was previously saved as integer
|
||||
color=ColorPreference.PINK;
|
||||
}
|
||||
|
||||
if(prefs.getInt("migrationLevel", 0) < 61) migrateToUpstreamVersion61();
|
||||
if(prefs.getInt("migrationLevel", 0) < 101) migrateToVersion101();
|
||||
}
|
||||
|
||||
public static void save(){
|
||||
@@ -171,7 +164,6 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("spectatorMode", spectatorMode)
|
||||
.putBoolean("autoHideFab", autoHideFab)
|
||||
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
||||
.putString("color", color.name())
|
||||
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
||||
.putBoolean("forwardReportDefault", forwardReportDefault)
|
||||
@@ -185,6 +177,16 @@ public class GlobalUserPreferences{
|
||||
.apply();
|
||||
}
|
||||
|
||||
private static void migrateToVersion101(){
|
||||
Log.d(TAG, "Migrating preferences to version 101!! (copy current theme to local preferences)");
|
||||
|
||||
AccountSessionManager asm=AccountSessionManager.getInstance();
|
||||
for(AccountSession session : asm.getLoggedInAccounts()){
|
||||
String accountID=session.getID();
|
||||
AccountLocalPreferences localPrefs=session.getLocalPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
private static void migrateToUpstreamVersion61(){
|
||||
Log.d(TAG, "Migrating preferences to upstream version 61!!");
|
||||
|
||||
@@ -235,30 +237,6 @@ public class GlobalUserPreferences{
|
||||
prefs.edit().putInt("migrationLevel", 61).apply();
|
||||
}
|
||||
|
||||
public enum ColorPreference{
|
||||
MATERIAL3,
|
||||
PINK,
|
||||
PURPLE,
|
||||
GREEN,
|
||||
BLUE,
|
||||
BROWN,
|
||||
RED,
|
||||
YELLOW;
|
||||
|
||||
public @StringRes int getName() {
|
||||
return switch(this){
|
||||
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
||||
case PINK -> R.string.sk_color_palette_pink;
|
||||
case PURPLE -> R.string.sk_color_palette_purple;
|
||||
case GREEN -> R.string.sk_color_palette_green;
|
||||
case BLUE -> R.string.sk_color_palette_blue;
|
||||
case BROWN -> R.string.sk_color_palette_brown;
|
||||
case RED -> R.string.sk_color_palette_red;
|
||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ThemePreference{
|
||||
AUTO,
|
||||
LIGHT,
|
||||
|
||||
@@ -39,7 +39,8 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||
UiUtils.setUserPreferredTheme(this);
|
||||
AccountSession session=getCurrentSession();
|
||||
UiUtils.setUserPreferredTheme(this, session);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if(savedInstanceState==null){
|
||||
@@ -217,6 +218,36 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
||||
}
|
||||
|
||||
public AccountSession getCurrentSession(){
|
||||
AccountSession session;
|
||||
Bundle args=new Bundle();
|
||||
Intent intent=getIntent();
|
||||
if(intent.hasExtra("fromExternalShare")) {
|
||||
return AccountSessionManager.getInstance()
|
||||
.getAccount(intent.getStringExtra("account"));
|
||||
}
|
||||
|
||||
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
||||
boolean hasNotification = intent.hasExtra("notification");
|
||||
if(fromNotification){
|
||||
String accountID=intent.getStringExtra("accountID");
|
||||
try{
|
||||
session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
if(!hasNotification) args.putString("tab", "notifications");
|
||||
}catch(IllegalStateException x){
|
||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
}
|
||||
}else{
|
||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
public void restartActivity(){
|
||||
finish();
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
}
|
||||
|
||||
public void restartHomeFragment(){
|
||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
||||
|
||||
@@ -6,8 +6,11 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
|
||||
@@ -39,6 +42,7 @@ public class AccountLocalPreferences{
|
||||
|
||||
public boolean emojiReactionsEnabled;
|
||||
public ShowEmojiReactions showEmojiReactions;
|
||||
public ColorPreference color;
|
||||
|
||||
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
|
||||
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
|
||||
@@ -66,6 +70,7 @@ public class AccountLocalPreferences{
|
||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
|
||||
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.MATERIAL3.name()));
|
||||
}
|
||||
|
||||
public long getNotificationsPauseEndTime(){
|
||||
@@ -99,9 +104,34 @@ public class AccountLocalPreferences{
|
||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
|
||||
.putString("showEmojiReactions", showEmojiReactions.name())
|
||||
.putString("color", color.name())
|
||||
.apply();
|
||||
}
|
||||
|
||||
public enum ColorPreference{
|
||||
MATERIAL3,
|
||||
PINK,
|
||||
PURPLE,
|
||||
GREEN,
|
||||
BLUE,
|
||||
BROWN,
|
||||
RED,
|
||||
YELLOW;
|
||||
|
||||
public @StringRes int getName() {
|
||||
return switch(this){
|
||||
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
||||
case PINK -> R.string.sk_color_palette_pink;
|
||||
case PURPLE -> R.string.sk_color_palette_purple;
|
||||
case GREEN -> R.string.sk_color_palette_green;
|
||||
case BLUE -> R.string.sk_color_palette_blue;
|
||||
case BROWN -> R.string.sk_color_palette_brown;
|
||||
case RED -> R.string.sk_color_palette_red;
|
||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShowEmojiReactions{
|
||||
HIDE_EMPTY,
|
||||
ONLY_OPENED,
|
||||
|
||||
@@ -26,12 +26,12 @@ import android.widget.ImageView;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.utils.ColorPalette;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -54,16 +54,16 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
attachmentID=getArguments().getString("attachment");
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
accountID=getArguments().getString("account");
|
||||
attachmentID=getArguments().getString("attachment");
|
||||
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
||||
ColorPalette.palettes.get(GlobalUserPreferences.color).apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
|
||||
ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().color).apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
|
||||
setTitle(R.string.add_alt_text);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
|
||||
@@ -131,7 +132,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
}
|
||||
|
||||
private @StringRes int getColorPaletteValue(){
|
||||
return switch(GlobalUserPreferences.color){
|
||||
return switch(AccountSessionManager.get(accountID).getLocalPreferences().color){
|
||||
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
||||
case PINK -> R.string.sk_color_palette_pink;
|
||||
case PURPLE -> R.string.sk_color_palette_purple;
|
||||
@@ -196,18 +197,19 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
}
|
||||
|
||||
private void onColorClick(){
|
||||
int selected=GlobalUserPreferences.color.ordinal();
|
||||
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
||||
int selected=prefs.color.ordinal();
|
||||
int[] newSelected={selected};
|
||||
String[] names=Arrays.stream(GlobalUserPreferences.ColorPreference.values()).map(GlobalUserPreferences.ColorPreference::getName).map(this::getString).toArray(String[]::new);
|
||||
String[] names=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).toArray(String[]::new);
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.settings_theme)
|
||||
.setSingleChoiceItems(names,
|
||||
selected, (dlg, item)->newSelected[0]=item)
|
||||
.setPositiveButton(R.string.ok, (dlg, item)->{
|
||||
GlobalUserPreferences.ColorPreference pref=GlobalUserPreferences.ColorPreference.values()[newSelected[0]];
|
||||
if(pref!=GlobalUserPreferences.color){
|
||||
GlobalUserPreferences.ColorPreference prev=GlobalUserPreferences.color;
|
||||
GlobalUserPreferences.color=pref;
|
||||
ColorPreference pref=ColorPreference.values()[newSelected[0]];
|
||||
if(pref!=prefs.color){
|
||||
ColorPreference prev=prefs.color;
|
||||
prefs.color=pref;
|
||||
GlobalUserPreferences.save();
|
||||
colorItem.subtitleRes=getColorPaletteValue();
|
||||
rebindItem(colorItem);
|
||||
@@ -257,17 +259,18 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
.show();
|
||||
}
|
||||
|
||||
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, GlobalUserPreferences.ColorPreference prevColor, Boolean prevTrueBlack){
|
||||
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, ColorPreference prevColor, Boolean prevTrueBlack){
|
||||
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
||||
if(prevTheme==null) prevTheme=GlobalUserPreferences.theme;
|
||||
if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme;
|
||||
if(prevColor==null) prevColor=GlobalUserPreferences.color;
|
||||
if(prevColor==null) prevColor=prefs.color;
|
||||
|
||||
boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK ||
|
||||
(prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
|
||||
boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK ||
|
||||
(GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
|
||||
boolean isNewBlack=GlobalUserPreferences.trueBlackTheme;
|
||||
if(isCurrentDark!=isNewDark || prevColor!=GlobalUserPreferences.color || (isNewDark && prevTrueBlack!=isNewBlack)){
|
||||
if(isCurrentDark!=isNewDark || prevColor!=prefs.color || (isNewDark && prevTrueBlack!=isNewBlack)){
|
||||
restartActivityToApplyNewTheme();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
||||
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
|
||||
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
|
||||
loggedOut=true;
|
||||
((MainActivity)getActivity()).restartHomeFragment();
|
||||
((MainActivity)getActivity()).restartActivity();
|
||||
}))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
|
||||
@@ -148,7 +148,7 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
private void logOut(String accountID){
|
||||
AccountSessionManager.get(accountID).logOut(activity, ()->{
|
||||
dismiss();
|
||||
((MainActivity)activity).restartHomeFragment();
|
||||
((MainActivity)activity).restartActivity();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
}
|
||||
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null){
|
||||
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
|
||||
((MainActivity)activity).restartHomeFragment();
|
||||
((MainActivity)activity).restartActivity();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.ThemePreference;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
|
||||
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.*;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
@@ -11,20 +11,21 @@ import androidx.annotation.StyleRes;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ColorPalette {
|
||||
public static final Map<GlobalUserPreferences.ColorPreference, ColorPalette> palettes = Map.of(
|
||||
ColorPreference.MATERIAL3, new ColorPalette(R.style.ColorPalette_Material3)
|
||||
public static final Map<AccountLocalPreferences.ColorPreference, ColorPalette> palettes = Map.of(
|
||||
MATERIAL3, new ColorPalette(R.style.ColorPalette_Material3)
|
||||
.dark(R.style.ColorPalette_Material3_Dark, R.style.ColorPalette_Material3_AutoLightDark),
|
||||
ColorPreference.PINK, new ColorPalette(R.style.ColorPalette_Pink),
|
||||
ColorPreference.PURPLE, new ColorPalette(R.style.ColorPalette_Purple),
|
||||
ColorPreference.GREEN, new ColorPalette(R.style.ColorPalette_Green),
|
||||
ColorPreference.BLUE, new ColorPalette(R.style.ColorPalette_Blue),
|
||||
ColorPreference.BROWN, new ColorPalette(R.style.ColorPalette_Brown),
|
||||
ColorPreference.RED, new ColorPalette(R.style.ColorPalette_Red),
|
||||
ColorPreference.YELLOW, new ColorPalette(R.style.ColorPalette_Yellow)
|
||||
PINK, new ColorPalette(R.style.ColorPalette_Pink),
|
||||
PURPLE, new ColorPalette(R.style.ColorPalette_Purple),
|
||||
GREEN, new ColorPalette(R.style.ColorPalette_Green),
|
||||
BLUE, new ColorPalette(R.style.ColorPalette_Blue),
|
||||
BROWN, new ColorPalette(R.style.ColorPalette_Brown),
|
||||
RED, new ColorPalette(R.style.ColorPalette_Red),
|
||||
YELLOW, new ColorPalette(R.style.ColorPalette_Yellow)
|
||||
);
|
||||
|
||||
private @StyleRes int base;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import static android.view.Menu.NONE;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.ThemePreference.*;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.theme;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
|
||||
|
||||
@@ -85,6 +86,7 @@ import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||
@@ -973,14 +975,20 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static void setUserPreferredTheme(Context context) {
|
||||
context.setTheme(switch (theme) {
|
||||
setUserPreferredTheme(context, null);
|
||||
}
|
||||
|
||||
public static void setUserPreferredTheme(Context context, @Nullable AccountSession session) {
|
||||
context.setTheme(switch(theme) {
|
||||
case LIGHT -> R.style.Theme_Mastodon_Light;
|
||||
case DARK -> R.style.Theme_Mastodon_Dark;
|
||||
default -> R.style.Theme_Mastodon_AutoLightDark;
|
||||
});
|
||||
|
||||
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
|
||||
if (palette != null) palette.apply(context);
|
||||
AccountLocalPreferences prefs=session.getLocalPreferences();
|
||||
AccountLocalPreferences.ColorPreference color=prefs != null ? prefs.color : AccountLocalPreferences.ColorPreference.MATERIAL3;
|
||||
ColorPalette palette = ColorPalette.palettes.get(color);
|
||||
if (palette != null) palette.apply(context, theme);
|
||||
|
||||
Resources res = context.getResources();
|
||||
MAX_WIDTH = (int) res.getDimension(R.dimen.layout_max_width);
|
||||
@@ -997,9 +1005,9 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static boolean isDarkTheme() {
|
||||
if (theme == GlobalUserPreferences.ThemePreference.AUTO)
|
||||
if (theme == AUTO)
|
||||
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
|
||||
return theme == GlobalUserPreferences.ThemePreference.DARK;
|
||||
return theme == DARK;
|
||||
}
|
||||
|
||||
public static Optional<Pair<String, Optional<String>>> parseFediverseHandle(String maybeFediHandle) {
|
||||
|
||||
Reference in New Issue
Block a user