mirror of
https://github.com/lichess-org/mobile.git
synced 2026-05-26 13:50:52 +00:00
@@ -13,7 +13,6 @@ targets:
|
||||
riverpod_generator:
|
||||
generate_for:
|
||||
- lib/src/localizations.dart
|
||||
- lib/src/theme.dart
|
||||
- lib/src/model/**/*.dart
|
||||
- lib/src/network/*.dart
|
||||
- lib/src/db/*.dart
|
||||
|
||||
+3
-3
@@ -10,12 +10,12 @@ import 'package:lichess_mobile/src/model/challenge/challenge_service.dart';
|
||||
import 'package:lichess_mobile/src/model/common/preloaded_data.dart';
|
||||
import 'package:lichess_mobile/src/model/correspondence/correspondence_service.dart';
|
||||
import 'package:lichess_mobile/src/model/notifications/notification_service.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/general_preferences.dart';
|
||||
import 'package:lichess_mobile/src/navigation.dart';
|
||||
import 'package:lichess_mobile/src/network/connectivity.dart';
|
||||
import 'package:lichess_mobile/src/network/http.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/styles/styles.dart';
|
||||
import 'package:lichess_mobile/src/theme.dart';
|
||||
import 'package:lichess_mobile/src/utils/navigation.dart';
|
||||
import 'package:lichess_mobile/src/utils/screen.dart';
|
||||
@@ -119,7 +119,8 @@ class _AppState extends ConsumerState<Application> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final generalPrefs = ref.watch(generalPreferencesProvider);
|
||||
final (light: themeLight, dark: themeDark) = ref.watch(applicationThemeProvider);
|
||||
final boardPrefs = ref.watch(boardPreferencesProvider);
|
||||
final (light: themeLight, dark: themeDark) = makeAppTheme(context, generalPrefs, boardPrefs);
|
||||
|
||||
final isIOS = Theme.of(context).platform == TargetPlatform.iOS;
|
||||
final remainingHeight = estimateRemainingHeightLeftBoard(context);
|
||||
@@ -138,7 +139,6 @@ class _AppState extends ConsumerState<Application> {
|
||||
navigationBarTheme: NavigationBarTheme.of(
|
||||
context,
|
||||
).copyWith(height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null),
|
||||
extensions: [lichessCustomColors.harmonized(themeDark.colorScheme)],
|
||||
),
|
||||
themeMode:
|
||||
generalPrefs.isForcedDarkMode
|
||||
|
||||
@@ -49,14 +49,6 @@ abstract class Styles {
|
||||
// cards
|
||||
static const cardBorderRadius = BorderRadius.all(Radius.circular(12.0));
|
||||
|
||||
static Color cardColor(BuildContext context) {
|
||||
final brightness = Theme.of(context).brightness;
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
return brightness == Brightness.light
|
||||
? colorScheme.surfaceContainerLowest
|
||||
: colorScheme.surfaceContainerHigh;
|
||||
}
|
||||
|
||||
// boards
|
||||
static const boardBorderRadius = BorderRadius.all(Radius.circular(5.0));
|
||||
|
||||
|
||||
+145
-138
@@ -1,157 +1,164 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/general_preferences.dart';
|
||||
import 'package:lichess_mobile/src/styles/styles.dart';
|
||||
import 'package:lichess_mobile/src/utils/color_palette.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'theme.g.dart';
|
||||
/// Makes the app theme based on the given [generalPrefs] and [boardPrefs] and the current [context].
|
||||
({ThemeData light, ThemeData dark}) makeAppTheme(
|
||||
BuildContext context,
|
||||
GeneralPrefs generalPrefs,
|
||||
BoardPrefs boardPrefs,
|
||||
) {
|
||||
final isIOS = Theme.of(context).platform == TargetPlatform.iOS;
|
||||
|
||||
@riverpod
|
||||
class ApplicationTheme extends _$ApplicationTheme {
|
||||
@override
|
||||
({ThemeData light, ThemeData dark}) build() {
|
||||
final generalPrefs = ref.watch(generalPreferencesProvider);
|
||||
final boardPrefs = ref.watch(boardPreferencesProvider);
|
||||
final isIOS = defaultTargetPlatform == TargetPlatform.iOS;
|
||||
|
||||
if (generalPrefs.backgroundTheme == null && generalPrefs.backgroundImage == null) {
|
||||
return _makeDefaultTheme(generalPrefs, boardPrefs, isIOS);
|
||||
} else if (generalPrefs.backgroundImage != null) {
|
||||
return _makeBackgroundImageTheme(
|
||||
baseTheme: generalPrefs.backgroundImage!.baseTheme,
|
||||
isIOS: isIOS,
|
||||
);
|
||||
} else {
|
||||
return _makeBackgroundImageTheme(
|
||||
baseTheme: generalPrefs.backgroundTheme!.baseTheme,
|
||||
isIOS: isIOS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
({ThemeData light, ThemeData dark}) _makeDefaultTheme(
|
||||
GeneralPrefs generalPrefs,
|
||||
BoardPrefs boardPrefs,
|
||||
bool isIOS,
|
||||
) {
|
||||
final boardTheme = boardPrefs.boardTheme;
|
||||
final systemScheme = getDynamicColorSchemes();
|
||||
final hasSystemColors = systemScheme != null && generalPrefs.systemColors == true;
|
||||
final defaultLight = ColorScheme.fromSeed(seedColor: boardTheme.colors.darkSquare);
|
||||
final defaultDark = ColorScheme.fromSeed(
|
||||
seedColor: boardTheme.colors.darkSquare,
|
||||
brightness: Brightness.dark,
|
||||
if (generalPrefs.backgroundTheme == null && generalPrefs.backgroundImage == null) {
|
||||
return _makeDefaultTheme(generalPrefs, boardPrefs, isIOS);
|
||||
} else if (generalPrefs.backgroundImage != null) {
|
||||
return _makeBackgroundImageTheme(
|
||||
baseTheme: generalPrefs.backgroundImage!.baseTheme,
|
||||
isIOS: isIOS,
|
||||
);
|
||||
|
||||
final themeLight =
|
||||
hasSystemColors
|
||||
? ThemeData.from(colorScheme: systemScheme.light)
|
||||
: ThemeData.from(colorScheme: defaultLight);
|
||||
final themeDark =
|
||||
hasSystemColors
|
||||
? ThemeData.from(colorScheme: systemScheme.dark)
|
||||
: ThemeData.from(colorScheme: defaultDark);
|
||||
|
||||
final lightCupertino = CupertinoThemeData(
|
||||
applyThemeToAll: true,
|
||||
primaryColor: themeLight.colorScheme.primary,
|
||||
primaryContrastingColor: themeLight.colorScheme.onPrimary,
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: darken(themeLight.scaffoldBackgroundColor, 0.05),
|
||||
barBackgroundColor: themeLight.colorScheme.surface.withValues(alpha: 0.9),
|
||||
textTheme: cupertinoTextTheme(themeLight.colorScheme),
|
||||
);
|
||||
|
||||
final darkCupertino = CupertinoThemeData(
|
||||
applyThemeToAll: true,
|
||||
primaryColor: themeDark.colorScheme.primary,
|
||||
primaryContrastingColor: themeDark.colorScheme.onPrimary,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: lighten(themeDark.scaffoldBackgroundColor, 0.04),
|
||||
barBackgroundColor: themeDark.colorScheme.surface.withValues(alpha: 0.9),
|
||||
textTheme: cupertinoTextTheme(themeDark.colorScheme),
|
||||
);
|
||||
|
||||
return (
|
||||
light: themeLight.copyWith(
|
||||
cupertinoOverrideTheme: lightCupertino,
|
||||
splashFactory: isIOS ? NoSplash.splashFactory : null,
|
||||
listTileTheme: isIOS ? _cupertinoListTileTheme(lightCupertino) : null,
|
||||
menuTheme: isIOS ? Styles.cupertinoAnchorMenuTheme : null,
|
||||
extensions: [lichessCustomColors.harmonized(themeLight.colorScheme)],
|
||||
),
|
||||
dark: themeDark.copyWith(
|
||||
cupertinoOverrideTheme: darkCupertino,
|
||||
splashFactory: isIOS ? NoSplash.splashFactory : null,
|
||||
listTileTheme: isIOS ? _cupertinoListTileTheme(darkCupertino) : null,
|
||||
menuTheme: isIOS ? Styles.cupertinoAnchorMenuTheme : null,
|
||||
extensions: [lichessCustomColors.harmonized(themeDark.colorScheme)],
|
||||
),
|
||||
} else {
|
||||
return _makeBackgroundImageTheme(
|
||||
baseTheme: generalPrefs.backgroundTheme!.baseTheme,
|
||||
isIOS: isIOS,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
({ThemeData light, ThemeData dark}) _makeBackgroundImageTheme({
|
||||
required ThemeData baseTheme,
|
||||
required bool isIOS,
|
||||
}) {
|
||||
final primary = baseTheme.colorScheme.primary;
|
||||
final onPrimary = baseTheme.colorScheme.onPrimary;
|
||||
final cupertinoTheme = CupertinoThemeData(
|
||||
primaryColor: primary,
|
||||
primaryContrastingColor: onPrimary,
|
||||
brightness: Brightness.dark,
|
||||
textTheme: cupertinoTextTheme(baseTheme.colorScheme),
|
||||
scaffoldBackgroundColor: baseTheme.scaffoldBackgroundColor.withValues(alpha: 0),
|
||||
barBackgroundColor: baseTheme.colorScheme.surface.withValues(alpha: 0.5),
|
||||
applyThemeToAll: true,
|
||||
);
|
||||
({ThemeData light, ThemeData dark}) _makeDefaultTheme(
|
||||
GeneralPrefs generalPrefs,
|
||||
BoardPrefs boardPrefs,
|
||||
bool isIOS,
|
||||
) {
|
||||
final boardTheme = boardPrefs.boardTheme;
|
||||
final systemScheme = getDynamicColorSchemes();
|
||||
final hasSystemColors = systemScheme != null && generalPrefs.systemColors == true;
|
||||
final defaultLight = ColorScheme.fromSeed(seedColor: boardTheme.colors.darkSquare);
|
||||
final defaultDark = ColorScheme.fromSeed(
|
||||
seedColor: boardTheme.colors.darkSquare,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
|
||||
const baseSurfaceAlpha = 0.7;
|
||||
final themeLight =
|
||||
hasSystemColors
|
||||
? ThemeData.from(colorScheme: systemScheme.light)
|
||||
: ThemeData.from(colorScheme: defaultLight);
|
||||
final themeDark =
|
||||
hasSystemColors
|
||||
? ThemeData.from(colorScheme: systemScheme.dark)
|
||||
: ThemeData.from(colorScheme: defaultDark);
|
||||
|
||||
final theme = baseTheme.copyWith(
|
||||
colorScheme: baseTheme.colorScheme.copyWith(
|
||||
surface: baseTheme.colorScheme.surface.withValues(alpha: baseSurfaceAlpha),
|
||||
surfaceContainerLowest: baseTheme.colorScheme.surfaceContainerLowest.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceContainerLow: baseTheme.colorScheme.surfaceContainerLow.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceContainer: baseTheme.colorScheme.surfaceContainer.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceContainerHigh: baseTheme.colorScheme.surfaceContainerHigh.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceContainerHighest: baseTheme.colorScheme.surfaceContainerHighest.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceDim: baseTheme.colorScheme.surfaceDim.withValues(alpha: baseSurfaceAlpha + 1),
|
||||
surfaceBright: baseTheme.colorScheme.surfaceBright.withValues(alpha: baseSurfaceAlpha - 2),
|
||||
),
|
||||
cupertinoOverrideTheme: cupertinoTheme,
|
||||
listTileTheme: isIOS ? _cupertinoListTileTheme(cupertinoTheme) : null,
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: baseTheme.colorScheme.surface.withValues(alpha: 0.8),
|
||||
),
|
||||
dialogTheme: DialogTheme(
|
||||
backgroundColor: baseTheme.colorScheme.surface.withValues(alpha: 0.8),
|
||||
),
|
||||
menuTheme: isIOS ? Styles.cupertinoAnchorMenuTheme : null,
|
||||
scaffoldBackgroundColor: baseTheme.scaffoldBackgroundColor.withValues(alpha: 0),
|
||||
appBarTheme: baseTheme.appBarTheme.copyWith(
|
||||
backgroundColor: baseTheme.scaffoldBackgroundColor.withValues(alpha: 0),
|
||||
),
|
||||
final lightCupertino = CupertinoThemeData(
|
||||
applyThemeToAll: true,
|
||||
primaryColor: themeLight.colorScheme.primary,
|
||||
primaryContrastingColor: themeLight.colorScheme.onPrimary,
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: darken(themeLight.scaffoldBackgroundColor, 0.05),
|
||||
barBackgroundColor: themeLight.colorScheme.surface.withValues(alpha: 0.9),
|
||||
textTheme: cupertinoTextTheme(themeLight.colorScheme),
|
||||
);
|
||||
|
||||
final darkCupertino = CupertinoThemeData(
|
||||
applyThemeToAll: true,
|
||||
primaryColor: themeDark.colorScheme.primary,
|
||||
primaryContrastingColor: themeDark.colorScheme.onPrimary,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: lighten(themeDark.scaffoldBackgroundColor, 0.04),
|
||||
barBackgroundColor: themeDark.colorScheme.surface.withValues(alpha: 0.9),
|
||||
textTheme: cupertinoTextTheme(themeDark.colorScheme),
|
||||
);
|
||||
|
||||
return (
|
||||
light: themeLight.copyWith(
|
||||
cupertinoOverrideTheme: lightCupertino,
|
||||
splashFactory: isIOS ? NoSplash.splashFactory : null,
|
||||
textTheme: isIOS ? Typography.whiteCupertino : null,
|
||||
extensions: [lichessCustomColors.harmonized(baseTheme.colorScheme)],
|
||||
);
|
||||
cardTheme:
|
||||
isIOS
|
||||
? CardTheme(
|
||||
color: themeLight.colorScheme.surfaceContainerLowest,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
)
|
||||
: null,
|
||||
listTileTheme: isIOS ? _cupertinoListTileTheme(lightCupertino) : null,
|
||||
menuTheme: isIOS ? Styles.cupertinoAnchorMenuTheme : null,
|
||||
extensions: [lichessCustomColors.harmonized(themeLight.colorScheme)],
|
||||
),
|
||||
dark: themeDark.copyWith(
|
||||
cupertinoOverrideTheme: darkCupertino,
|
||||
splashFactory: isIOS ? NoSplash.splashFactory : null,
|
||||
cardTheme:
|
||||
isIOS
|
||||
? CardTheme(
|
||||
color: themeDark.colorScheme.surfaceContainerHigh,
|
||||
elevation: 0,
|
||||
margin: EdgeInsets.zero,
|
||||
)
|
||||
: null,
|
||||
listTileTheme: isIOS ? _cupertinoListTileTheme(darkCupertino) : null,
|
||||
menuTheme: isIOS ? Styles.cupertinoAnchorMenuTheme : null,
|
||||
extensions: [lichessCustomColors.harmonized(themeDark.colorScheme)],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return (light: theme, dark: theme);
|
||||
}
|
||||
({ThemeData light, ThemeData dark}) _makeBackgroundImageTheme({
|
||||
required ThemeData baseTheme,
|
||||
required bool isIOS,
|
||||
}) {
|
||||
final primary = baseTheme.colorScheme.primary;
|
||||
final onPrimary = baseTheme.colorScheme.onPrimary;
|
||||
final cupertinoTheme = CupertinoThemeData(
|
||||
primaryColor: primary,
|
||||
primaryContrastingColor: onPrimary,
|
||||
brightness: Brightness.dark,
|
||||
textTheme: cupertinoTextTheme(baseTheme.colorScheme),
|
||||
scaffoldBackgroundColor: baseTheme.scaffoldBackgroundColor.withValues(alpha: 0),
|
||||
barBackgroundColor: baseTheme.colorScheme.surface.withValues(alpha: 0.5),
|
||||
applyThemeToAll: true,
|
||||
);
|
||||
|
||||
const baseSurfaceAlpha = 0.7;
|
||||
|
||||
final theme = baseTheme.copyWith(
|
||||
colorScheme: baseTheme.colorScheme.copyWith(
|
||||
surface: baseTheme.colorScheme.surface.withValues(alpha: baseSurfaceAlpha),
|
||||
surfaceContainerLowest: baseTheme.colorScheme.surfaceContainerLowest.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceContainerLow: baseTheme.colorScheme.surfaceContainerLow.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceContainer: baseTheme.colorScheme.surfaceContainer.withValues(alpha: baseSurfaceAlpha),
|
||||
surfaceContainerHigh: baseTheme.colorScheme.surfaceContainerHigh.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceContainerHighest: baseTheme.colorScheme.surfaceContainerHighest.withValues(
|
||||
alpha: baseSurfaceAlpha,
|
||||
),
|
||||
surfaceDim: baseTheme.colorScheme.surfaceDim.withValues(alpha: baseSurfaceAlpha + 1),
|
||||
surfaceBright: baseTheme.colorScheme.surfaceBright.withValues(alpha: baseSurfaceAlpha - 2),
|
||||
),
|
||||
cupertinoOverrideTheme: cupertinoTheme,
|
||||
listTileTheme: isIOS ? _cupertinoListTileTheme(cupertinoTheme) : null,
|
||||
cardTheme: isIOS ? const CardTheme(elevation: 0, margin: EdgeInsets.zero) : null,
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: baseTheme.colorScheme.surface.withValues(alpha: 0.8),
|
||||
),
|
||||
dialogTheme: DialogTheme(backgroundColor: baseTheme.colorScheme.surface.withValues(alpha: 0.8)),
|
||||
menuTheme: isIOS ? Styles.cupertinoAnchorMenuTheme : null,
|
||||
scaffoldBackgroundColor: baseTheme.scaffoldBackgroundColor.withValues(alpha: 0),
|
||||
appBarTheme: baseTheme.appBarTheme.copyWith(
|
||||
backgroundColor: baseTheme.scaffoldBackgroundColor.withValues(alpha: 0),
|
||||
),
|
||||
splashFactory: isIOS ? NoSplash.splashFactory : null,
|
||||
extensions: [lichessCustomColors.harmonized(baseTheme.colorScheme)],
|
||||
);
|
||||
|
||||
return (light: theme, dark: theme);
|
||||
}
|
||||
|
||||
/// Makes a Cupertino text theme based on the given [colors].
|
||||
|
||||
@@ -232,7 +232,10 @@ class _BroadcastCarouselItemState extends State<BroadcastCarouselItem> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundColor = _cardColors?.primaryContainer ?? Styles.cardColor(context);
|
||||
final backgroundColor =
|
||||
_cardColors?.primaryContainer ??
|
||||
Theme.of(context).cardTheme.color ??
|
||||
Theme.of(context).colorScheme.surfaceContainerLow;
|
||||
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||
final double width = screenWidth - 16.0;
|
||||
final paddingWidth = kBroadcastCarouselItemPadding.horizontal;
|
||||
|
||||
@@ -140,7 +140,10 @@ class _ChoiceChipState extends State<_ChoiceChip> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bgColor = Styles.cardColor(context);
|
||||
final bgColor =
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Theme.of(context).colorScheme.surfaceContainerLowest
|
||||
: Theme.of(context).colorScheme.surfaceContainerHigh;
|
||||
|
||||
return Opacity(
|
||||
opacity: widget.onTap != null ? 1.0 : 0.5,
|
||||
|
||||
@@ -207,7 +207,10 @@ class ListSection extends StatelessWidget {
|
||||
clipBehavior: cupertinoClipBehavior,
|
||||
backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? Styles.cardColor(context),
|
||||
color:
|
||||
backgroundColor ??
|
||||
theme.cardTheme.color ??
|
||||
theme.colorScheme.surfaceContainerLow,
|
||||
borderRadius:
|
||||
cupertinoBorderRadius ?? const BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
|
||||
@@ -85,7 +85,6 @@ class PlatformCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final platform = Theme.of(context).platform;
|
||||
return MediaQuery.withClampedTextScaling(
|
||||
maxScaleFactor: kCardTextScaleFactor,
|
||||
child: Card(
|
||||
@@ -93,11 +92,11 @@ class PlatformCard extends StatelessWidget {
|
||||
borderRadius != null
|
||||
? RoundedRectangleBorder(borderRadius: borderRadius!)
|
||||
: const RoundedRectangleBorder(borderRadius: Styles.cardBorderRadius),
|
||||
color: color ?? Styles.cardColor(context),
|
||||
color: color,
|
||||
shadowColor: shadowColor,
|
||||
semanticContainer: semanticContainer,
|
||||
elevation: elevation ?? (platform == TargetPlatform.iOS ? 0 : null),
|
||||
margin: margin ?? EdgeInsets.zero,
|
||||
elevation: elevation,
|
||||
margin: margin,
|
||||
clipBehavior: clipBehavior,
|
||||
child: child,
|
||||
),
|
||||
|
||||
@@ -270,7 +270,9 @@ class ChoicePicker<T> extends StatelessWidget {
|
||||
child: CupertinoListSection.insetGrouped(
|
||||
backgroundColor: CupertinoTheme.of(context).scaffoldBackgroundColor,
|
||||
decoration: BoxDecoration(
|
||||
color: Styles.cardColor(context),
|
||||
color:
|
||||
Theme.of(context).cardTheme.color ??
|
||||
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
separatorColor: Styles.cupertinoSeparatorColor.resolveFrom(context),
|
||||
|
||||
Reference in New Issue
Block a user