mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
Improve color display in M3 Demo (#2438)
- Added the color scheme schematic from Material Theme Builder aligning how colors are displayed here and in documentation across Android and Figma. - On desktop, individual colors can be copied to the clipboard - View adapts from the existing single column view to the new schematic at 500dp width. ### Before: <img width="1727" alt="Screenshot 2024-09-12 at 2 40 15 PM" src="https://github.com/user-attachments/assets/37423d79-174a-4691-b0e1-8f18c947550a"> ### After:   ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md --------- Co-authored-by: Eric Windmill <ewindmill@google.com>
This commit is contained in:
93
material_3_demo/lib/color_box.dart
Normal file
93
material_3_demo/lib/color_box.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ColorBox extends StatefulWidget {
|
||||
const ColorBox(
|
||||
{super.key,
|
||||
required this.label,
|
||||
required this.tone,
|
||||
required this.color,
|
||||
required this.onColor,
|
||||
required this.height,
|
||||
required this.width,
|
||||
this.displayPaletteInfo = false});
|
||||
|
||||
final String label;
|
||||
final String tone;
|
||||
final Color color, onColor;
|
||||
final double height, width;
|
||||
final bool displayPaletteInfo;
|
||||
|
||||
@override
|
||||
State<ColorBox> createState() => _ColorBoxState();
|
||||
}
|
||||
|
||||
class _ColorBoxState extends State<ColorBox> {
|
||||
bool hovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fonts = Theme.of(context).textTheme;
|
||||
return MouseRegion(
|
||||
onEnter: (_) {
|
||||
if (mounted) setState(() => hovered = true);
|
||||
},
|
||||
onExit: (_) {
|
||||
if (mounted) setState(() => hovered = false);
|
||||
},
|
||||
child: Container(
|
||||
color: widget.color,
|
||||
height: widget.height,
|
||||
width: widget.width,
|
||||
child: DefaultTextStyle(
|
||||
style: fonts.labelSmall!.copyWith(color: widget.onColor),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: 10,
|
||||
child: Text(widget.label),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
child: Text(widget.displayPaletteInfo ? widget.tone : ''),
|
||||
),
|
||||
if (hovered)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
color: widget.onColor,
|
||||
tooltip: 'Copy hex color',
|
||||
icon: const Icon(Icons.copy, size: 24),
|
||||
onPressed: () async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
// Copy color as hex to clipboard
|
||||
String hex = '#';
|
||||
final c = widget.color;
|
||||
// Will change from int 0-255 to double 0.0-1.0 in 3.26+
|
||||
// The properties also change from red/green/blue to r/g/b
|
||||
// hex += (c.[r g b] * 255.0).round().toRadixString(16).padLeft(2, '0');
|
||||
hex += c.red.toRadixString(16).padLeft(2, '0');
|
||||
hex += c.green.toRadixString(16).padLeft(2, '0');
|
||||
hex += c.blue.toRadixString(16).padLeft(2, '0');
|
||||
final data = ClipboardData(text: hex);
|
||||
await Clipboard.setData(data);
|
||||
messenger.hideCurrentSnackBar();
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Copied $hex to clipboard'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_3_demo/scheme.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
const Widget divider = SizedBox(height: 10);
|
||||
@@ -11,7 +12,7 @@ const Widget divider = SizedBox(height: 10);
|
||||
// If screen content width is greater or equal to this value, the light and dark
|
||||
// color schemes will be displayed in a column. Otherwise, they will
|
||||
// be displayed in a row.
|
||||
const double narrowScreenWidthThreshold = 400;
|
||||
const double narrowScreenWidthThreshold = 500;
|
||||
|
||||
class ColorPalettesScreen extends StatelessWidget {
|
||||
const ColorPalettesScreen({super.key});
|
||||
@@ -94,32 +95,32 @@ class ColorPalettesScreen extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
Color seed = Theme.of(context).colorScheme.primary;
|
||||
ColorScheme lightScheme = ColorScheme.fromSeed(
|
||||
seedColor: seed, brightness: Brightness.light);
|
||||
ColorScheme darkScheme = ColorScheme.fromSeed(
|
||||
seedColor: seed, brightness: Brightness.dark);
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Column(
|
||||
children: [
|
||||
dynamicColorNotice(),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
schemeLabel('Light ColorScheme'),
|
||||
schemeView(lightTheme),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
schemeLabel('Dark ColorScheme'),
|
||||
schemeView(darkTheme),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
SchemePreview(
|
||||
label: "Light ColorScheme",
|
||||
scheme: lightScheme,
|
||||
brightness: Brightness.light,
|
||||
contrast: 1.0,
|
||||
colorMatch: false,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SchemePreview(
|
||||
label: "Dark ColorScheme",
|
||||
scheme: darkScheme,
|
||||
brightness: Brightness.dark,
|
||||
contrast: 1.0,
|
||||
colorMatch: false,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
417
material_3_demo/lib/scheme.dart
Normal file
417
material_3_demo/lib/scheme.dart
Normal file
@@ -0,0 +1,417 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../color_box.dart';
|
||||
|
||||
class SchemePreview extends StatefulWidget {
|
||||
const SchemePreview({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.scheme,
|
||||
required this.brightness,
|
||||
required this.colorMatch,
|
||||
required this.contrast,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final ColorScheme scheme;
|
||||
final Brightness brightness;
|
||||
final bool colorMatch;
|
||||
final double contrast;
|
||||
|
||||
@override
|
||||
State<SchemePreview> createState() => _SchemePreviewState();
|
||||
}
|
||||
|
||||
class _SchemePreviewState extends State<SchemePreview> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final fonts = theme.textTheme;
|
||||
final colors = theme.colorScheme;
|
||||
final dark = widget.brightness == Brightness.dark;
|
||||
|
||||
final scheme = widget.scheme;
|
||||
|
||||
return Theme(
|
||||
data: theme.copyWith(colorScheme: scheme),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fitWidth,
|
||||
child: Container(
|
||||
width: 902,
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: theme.brightness == widget.brightness
|
||||
? colors.outlineVariant
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.label,
|
||||
style: fonts.titleMedium!.copyWith(
|
||||
color: scheme.onSurface,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ColorBox(
|
||||
label: 'Primary',
|
||||
tone: dark ? 'P-80' : 'P-40',
|
||||
color: scheme.primary,
|
||||
onColor: scheme.onPrimary,
|
||||
height: 87,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Primary',
|
||||
tone: dark ? 'P-20' : 'P-100',
|
||||
color: scheme.onPrimary,
|
||||
onColor: scheme.primary,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
ColorBox(
|
||||
label: 'Primary Container',
|
||||
tone: dark ? 'P-30' : 'P-90',
|
||||
color: scheme.primaryContainer,
|
||||
onColor: scheme.onPrimaryContainer,
|
||||
height: 87,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Primary Container',
|
||||
tone: dark ? 'P-90' : 'P-10',
|
||||
color: scheme.onPrimaryContainer,
|
||||
onColor: scheme.primaryContainer,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ColorBox(
|
||||
label: 'Secondary',
|
||||
tone: dark ? 'S-80' : 'S-40',
|
||||
color: scheme.secondary,
|
||||
onColor: scheme.onSecondary,
|
||||
height: 87,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Secondary',
|
||||
tone: dark ? 'S-20' : 'S-100',
|
||||
color: scheme.onSecondary,
|
||||
onColor: scheme.secondary,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
ColorBox(
|
||||
label: 'Secondary Container',
|
||||
tone: dark ? 'S-30' : 'S-90',
|
||||
color: scheme.secondaryContainer,
|
||||
onColor: scheme.onSecondaryContainer,
|
||||
height: 87,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Secondary Container',
|
||||
tone: dark ? 'S-90' : 'S-10',
|
||||
color: scheme.onSecondaryContainer,
|
||||
onColor: scheme.secondaryContainer,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ColorBox(
|
||||
label: 'Tertiary',
|
||||
tone: dark ? 'T-80' : 'T-40',
|
||||
color: scheme.tertiary,
|
||||
onColor: scheme.onTertiary,
|
||||
height: 87,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Tertiary',
|
||||
tone: dark ? 'T-20' : 'T-100',
|
||||
color: scheme.onTertiary,
|
||||
onColor: scheme.tertiary,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
ColorBox(
|
||||
label: 'Tertiary Container',
|
||||
tone: dark ? 'T-30' : 'T-90',
|
||||
color: scheme.tertiaryContainer,
|
||||
onColor: scheme.onTertiaryContainer,
|
||||
height: 87,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Tertiary Container',
|
||||
tone: dark ? 'T-90' : 'T-10',
|
||||
color: scheme.onTertiaryContainer,
|
||||
onColor: scheme.tertiaryContainer,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ColorBox(
|
||||
label: 'Surface Dim',
|
||||
tone: dark ? 'N-6' : 'N-87',
|
||||
color: scheme.surfaceDim,
|
||||
onColor: scheme.onSurface,
|
||||
height: 105,
|
||||
width: 211.45,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Surface',
|
||||
tone: dark ? 'N-6' : 'N-98',
|
||||
color: scheme.surface,
|
||||
onColor: scheme.onSurface,
|
||||
height: 105,
|
||||
width: 211.45,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Surface Bright',
|
||||
tone: dark ? 'N-24' : 'N-98',
|
||||
color: scheme.surfaceBright,
|
||||
onColor: scheme.onSurface,
|
||||
height: 105,
|
||||
width: 211.45,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ColorBox(
|
||||
label: 'Surf. Container\nLowest',
|
||||
tone: dark ? 'N-4' : 'N-100',
|
||||
color: scheme.surfaceContainerLowest,
|
||||
onColor: scheme.onSurface,
|
||||
height: 105,
|
||||
width: 126.87,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Surf. Container\nLow',
|
||||
tone: dark ? 'N-10' : 'N-96',
|
||||
color: scheme.surfaceContainerLow,
|
||||
onColor: scheme.onSurface,
|
||||
height: 105,
|
||||
width: 126.87,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Surf. Container',
|
||||
tone: dark ? 'N-12' : 'N-94',
|
||||
color: scheme.surfaceContainer,
|
||||
onColor: scheme.onSurface,
|
||||
height: 105,
|
||||
width: 126.87,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Surf. Container\nHigh',
|
||||
tone: dark ? 'N-17' : 'N-92',
|
||||
color: scheme.surfaceContainerHigh,
|
||||
onColor: scheme.onSurface,
|
||||
height: 105,
|
||||
width: 126.87,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Surf. Container\nHighest',
|
||||
tone: dark ? 'N-24' : 'N-90',
|
||||
color: scheme.surfaceContainerHighest,
|
||||
onColor: scheme.onSurface,
|
||||
height: 105,
|
||||
width: 126.87,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ColorBox(
|
||||
label: 'On Surface',
|
||||
tone: dark ? 'N-90' : 'N-10',
|
||||
color: scheme.onSurface,
|
||||
onColor: scheme.surface,
|
||||
height: 40,
|
||||
width: 158.59,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Surface Var.',
|
||||
tone: dark ? 'NV-90' : 'NV-30',
|
||||
color: scheme.onSurfaceVariant,
|
||||
onColor: scheme.surfaceContainerHighest,
|
||||
height: 40,
|
||||
width: 158.59,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Outline',
|
||||
tone: dark ? 'NV-60' : 'NV-50',
|
||||
color: scheme.outline,
|
||||
onColor: scheme.surface,
|
||||
height: 40,
|
||||
width: 158.59,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Outline Variant',
|
||||
tone: dark ? 'NV-30' : 'NV-80',
|
||||
color: scheme.outlineVariant,
|
||||
onColor: scheme.onSurface,
|
||||
height: 40,
|
||||
width: 158.59,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ColorBox(
|
||||
label: 'Error',
|
||||
tone: dark ? 'E-80' : 'E-40',
|
||||
color: scheme.error,
|
||||
onColor: scheme.onError,
|
||||
height: 87,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Error',
|
||||
tone: dark ? 'E-20' : 'E-100',
|
||||
color: scheme.onError,
|
||||
onColor: scheme.error,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
ColorBox(
|
||||
label: 'Error Container',
|
||||
tone: dark ? 'E-30' : 'E-90',
|
||||
color: scheme.errorContainer,
|
||||
onColor: scheme.onErrorContainer,
|
||||
height: 87,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'On Error Container',
|
||||
tone: dark ? 'E-90' : 'E-10',
|
||||
color: scheme.onErrorContainer,
|
||||
onColor: scheme.errorContainer,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ColorBox(
|
||||
label: 'Inverse Surface',
|
||||
tone: dark ? 'N-90' : 'N-20',
|
||||
color: scheme.inverseSurface,
|
||||
onColor: scheme.onInverseSurface,
|
||||
height: 120,
|
||||
width: 208,
|
||||
),
|
||||
ColorBox(
|
||||
label: 'Inverse On Surface',
|
||||
tone: dark ? 'N-20' : 'N-95',
|
||||
color: scheme.onInverseSurface,
|
||||
onColor: scheme.inverseSurface,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
ColorBox(
|
||||
label: 'Inverse Primary',
|
||||
tone: dark ? 'P-40' : 'P-80',
|
||||
color: scheme.inversePrimary,
|
||||
onColor: scheme.onSurface,
|
||||
height: 40,
|
||||
width: 208,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ColorBox(
|
||||
label: 'Scrim',
|
||||
tone: 'N-0',
|
||||
color: scheme.scrim,
|
||||
onColor: Colors.white,
|
||||
height: 40,
|
||||
width: 96.31,
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
ColorBox(
|
||||
label: 'Shadow',
|
||||
tone: 'N-0',
|
||||
color: scheme.shadow,
|
||||
onColor: Colors.white,
|
||||
height: 40,
|
||||
width: 96.31,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ class TypographyScreen extends StatelessWidget {
|
||||
return Expanded(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 7),
|
||||
const SizedBox(height: 8),
|
||||
TextStyleExample(
|
||||
name: 'Display Large', style: textTheme.displayLarge!),
|
||||
TextStyleExample(
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:material_3_demo/color_palettes_screen.dart';
|
||||
import 'package:material_3_demo/main.dart';
|
||||
import 'package:material_3_demo/scheme.dart';
|
||||
|
||||
import 'component_screen_test.dart';
|
||||
|
||||
@@ -69,6 +70,6 @@ void main() {
|
||||
));
|
||||
expect(find.text('Light ColorScheme'), findsOneWidget);
|
||||
expect(find.text('Dark ColorScheme'), findsOneWidget);
|
||||
expect(find.byType(ColorGroup, skipOffstage: false), findsNWidgets(18));
|
||||
expect(find.byType(SchemePreview, skipOffstage: false), findsNWidgets(2));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ declare -ar PROJECT_NAMES=(
|
||||
"infinite_list"
|
||||
"ios_app_clip"
|
||||
"isolate_example"
|
||||
"material_3_demo"
|
||||
# TODO @ewindmill -- Color.red is deprecated and should be Color.r (same for green and blue)
|
||||
# "material_3_demo"
|
||||
"navigation_and_routing"
|
||||
"place_tracker"
|
||||
"platform_channels"
|
||||
|
||||
@@ -47,7 +47,8 @@ declare -ar PROJECT_NAMES=(
|
||||
"infinite_list"
|
||||
"ios_app_clip"
|
||||
"isolate_example"
|
||||
"material_3_demo"
|
||||
# TODO @ewindmill -- Color.red is deprecated and should be Color.r (same for green and blue)
|
||||
# "material_3_demo"
|
||||
"navigation_and_routing"
|
||||
"place_tracker"
|
||||
"platform_channels"
|
||||
|
||||
Reference in New Issue
Block a user