Gallery UI tweaks (#4861)
This commit is contained in:
parent
4c93831136
commit
445f250c91
@ -87,7 +87,10 @@ class OrderItem extends StatelessWidget {
|
|||||||
items: <int>[0, 1, 2, 3, 4, 5].map((int value) {
|
items: <int>[0, 1, 2, 3, 4, 5].map((int value) {
|
||||||
return new DropDownMenuItem<int>(
|
return new DropDownMenuItem<int>(
|
||||||
value: value,
|
value: value,
|
||||||
child: new Text('Quantity $value', style: theme.quantityMenuStyle)
|
child: new Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
|
child: new Text('Quantity $value', style: theme.quantityMenuStyle)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
value: quantity,
|
value: quantity,
|
||||||
|
@ -48,11 +48,43 @@ class GalleryHome extends StatefulWidget {
|
|||||||
|
|
||||||
class GalleryHomeState extends State<GalleryHome> {
|
class GalleryHomeState extends State<GalleryHome> {
|
||||||
final Key _homeKey = new ValueKey<String>("Gallery Home");
|
final Key _homeKey = new ValueKey<String>("Gallery Home");
|
||||||
|
final List<Widget> _listItems = <Widget>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// The first item in the list just exists to occupy the space behind
|
||||||
|
// the flexible app bar. As it's scrolled out of the way, the app bar's
|
||||||
|
// height will shrink.
|
||||||
|
final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
|
||||||
|
_listItems.add(new SizedBox(height: _kFlexibleSpaceMaxHeight + statusBarHeight));
|
||||||
|
|
||||||
|
final ThemeData themeData = Theme.of(context);
|
||||||
|
final TextStyle headerStyle = themeData.textTheme.body2.copyWith(color: themeData.primaryColor);
|
||||||
|
String category;
|
||||||
|
for (GalleryItem galleryItem in kAllGalleryItems) {
|
||||||
|
if (category != galleryItem.category) {
|
||||||
|
if (category != null)
|
||||||
|
_listItems.add(new Divider());
|
||||||
|
_listItems.add(
|
||||||
|
new Container(
|
||||||
|
height: 48.0,
|
||||||
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
|
child: new Align(
|
||||||
|
alignment: FractionalOffset.centerLeft,
|
||||||
|
child: new Text(galleryItem.category, style: headerStyle)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
category = galleryItem.category;
|
||||||
|
}
|
||||||
|
_listItems.add(galleryItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final double statusBarHight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
|
|
||||||
|
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
key: _homeKey,
|
key: _homeKey,
|
||||||
drawer: new GalleryDrawer(
|
drawer: new GalleryDrawer(
|
||||||
@ -75,27 +107,7 @@ class GalleryHomeState extends State<GalleryHome> {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
appBarBehavior: AppBarBehavior.under,
|
appBarBehavior: AppBarBehavior.under,
|
||||||
body: new TwoLevelList(
|
body: new Block(children: _listItems)
|
||||||
padding: new EdgeInsets.only(top: _kFlexibleSpaceMaxHeight + statusBarHight),
|
|
||||||
type: MaterialListType.oneLine,
|
|
||||||
children: <Widget>[
|
|
||||||
new TwoLevelSublist(
|
|
||||||
leading: new Icon(Icons.star),
|
|
||||||
title: new Text('Demos'),
|
|
||||||
children: _demoItems
|
|
||||||
),
|
|
||||||
new TwoLevelSublist(
|
|
||||||
leading: new Icon(Icons.extension),
|
|
||||||
title: new Text('Components'),
|
|
||||||
children: _componentItems
|
|
||||||
),
|
|
||||||
new TwoLevelSublist(
|
|
||||||
leading: new Icon(Icons.color_lens),
|
|
||||||
title: new Text('Style'),
|
|
||||||
children: _styleItems
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import '../demo/all.dart';
|
|||||||
typedef Widget GalleryDemoBuilder();
|
typedef Widget GalleryDemoBuilder();
|
||||||
|
|
||||||
class GalleryItem extends StatelessWidget {
|
class GalleryItem extends StatelessWidget {
|
||||||
GalleryItem({ this.title, this.category: 'Components', this.routeName, this.buildRoute }) {
|
GalleryItem({ this.title, this.subtitle, this.category: 'Components', this.routeName, this.buildRoute }) {
|
||||||
assert(title != null);
|
assert(title != null);
|
||||||
assert(category != null);
|
assert(category != null);
|
||||||
assert(routeName != null);
|
assert(routeName != null);
|
||||||
@ -19,14 +19,16 @@ class GalleryItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
final String category;
|
final String category;
|
||||||
final String routeName;
|
final String routeName;
|
||||||
final WidgetBuilder buildRoute;
|
final WidgetBuilder buildRoute;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new TwoLevelListItem(
|
return new ListItem(
|
||||||
title: new Text(title),
|
title: new Text(title),
|
||||||
|
subtitle: new Text(subtitle),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (routeName != null) {
|
if (routeName != null) {
|
||||||
Timeline.instantSync('Start Transition', arguments: <String, String>{
|
Timeline.instantSync('Start Transition', arguments: <String, String>{
|
||||||
@ -44,168 +46,193 @@ final List<GalleryItem> kAllGalleryItems = <GalleryItem>[
|
|||||||
// Demos
|
// Demos
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Pesto',
|
title: 'Pesto',
|
||||||
|
subtitle: 'A simple recipe browser',
|
||||||
category: 'Demos',
|
category: 'Demos',
|
||||||
routeName: PestoDemo.routeName,
|
routeName: PestoDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new PestoDemo()
|
buildRoute: (BuildContext context) => new PestoDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Shrine',
|
title: 'Shrine',
|
||||||
|
subtitle:'A basic shopping app',
|
||||||
category: 'Demos',
|
category: 'Demos',
|
||||||
routeName: ShrineDemo.routeName,
|
routeName: ShrineDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ShrineDemo()
|
buildRoute: (BuildContext context) => new ShrineDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
|
||||||
title: 'Calculator',
|
|
||||||
category: 'Demos',
|
|
||||||
routeName: CalculatorDemo.routeName,
|
|
||||||
buildRoute: (BuildContext context) => new CalculatorDemo()
|
|
||||||
),
|
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Contacts',
|
title: 'Contacts',
|
||||||
category: 'Demos',
|
category: 'Demos',
|
||||||
|
subtitle: 'Highlights the flexible appbar',
|
||||||
routeName: ContactsDemo.routeName,
|
routeName: ContactsDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ContactsDemo()
|
buildRoute: (BuildContext context) => new ContactsDemo()
|
||||||
),
|
),
|
||||||
// Components
|
// Components
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Buttons',
|
title: 'Buttons',
|
||||||
|
subtitle: 'All kinds: flat, raised, dropdown, icon, etc',
|
||||||
routeName: ButtonsDemo.routeName,
|
routeName: ButtonsDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ButtonsDemo()
|
buildRoute: (BuildContext context) => new ButtonsDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Cards',
|
title: 'Cards',
|
||||||
|
subtitle: 'Material with rounded corners and a drop shadow',
|
||||||
routeName: CardsDemo.routeName,
|
routeName: CardsDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new CardsDemo()
|
buildRoute: (BuildContext context) => new CardsDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Chips',
|
title: 'Chips',
|
||||||
|
subtitle: 'A label with an optional delete button and avatar',
|
||||||
routeName: ChipDemo.routeName,
|
routeName: ChipDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ChipDemo()
|
buildRoute: (BuildContext context) => new ChipDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Date picker',
|
title: 'Date picker',
|
||||||
|
subtitle: 'Choose month, day, and year',
|
||||||
routeName: DatePickerDemo.routeName,
|
routeName: DatePickerDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new DatePickerDemo()
|
buildRoute: (BuildContext context) => new DatePickerDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Data tables',
|
title: 'Data tables',
|
||||||
|
subtitle: 'Full-featured grid',
|
||||||
routeName: DataTableDemo.routeName,
|
routeName: DataTableDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new DataTableDemo()
|
buildRoute: (BuildContext context) => new DataTableDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Dialog',
|
title: 'Dialog',
|
||||||
|
subtitle: 'All kinds: simple, alert, fullscreen, etc',
|
||||||
routeName: DialogDemo.routeName,
|
routeName: DialogDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new DialogDemo()
|
buildRoute: (BuildContext context) => new DialogDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Expand/collapse list control',
|
title: 'Expand/collapse list control',
|
||||||
|
subtitle: 'A list with one level of sublists',
|
||||||
routeName: TwoLevelListDemo.routeName,
|
routeName: TwoLevelListDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new TwoLevelListDemo()
|
buildRoute: (BuildContext context) => new TwoLevelListDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Floating action button',
|
title: 'Floating action button',
|
||||||
|
subtitle: 'Demos action button transitions',
|
||||||
routeName: TabsFabDemo.routeName,
|
routeName: TabsFabDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new TabsFabDemo()
|
buildRoute: (BuildContext context) => new TabsFabDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Grid',
|
title: 'Grid',
|
||||||
|
subtitle: 'Row and column layout',
|
||||||
routeName: GridListDemo.routeName,
|
routeName: GridListDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new GridListDemo()
|
buildRoute: (BuildContext context) => new GridListDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Icons',
|
title: 'Icons',
|
||||||
|
subtitle: 'Enabled and disabled icons with varying opacity',
|
||||||
routeName: IconsDemo.routeName,
|
routeName: IconsDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new IconsDemo()
|
buildRoute: (BuildContext context) => new IconsDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Leave-behind list items',
|
title: 'Leave-behind list items',
|
||||||
|
subtitle: 'Drag items to expose hidden actions',
|
||||||
routeName: LeaveBehindDemo.routeName,
|
routeName: LeaveBehindDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new LeaveBehindDemo()
|
buildRoute: (BuildContext context) => new LeaveBehindDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'List',
|
title: 'List',
|
||||||
|
subtitle: 'All the layout variations for scrollable lists',
|
||||||
routeName: ListDemo.routeName,
|
routeName: ListDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ListDemo()
|
buildRoute: (BuildContext context) => new ListDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Menus',
|
title: 'Menus',
|
||||||
|
subtitle: 'Menu buttons and simple menus',
|
||||||
routeName: MenuDemo.routeName,
|
routeName: MenuDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new MenuDemo()
|
buildRoute: (BuildContext context) => new MenuDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Modal bottom sheet',
|
title: 'Modal bottom sheet',
|
||||||
|
subtitle: 'A modal sheet that slides up from the bottom',
|
||||||
routeName: ModalBottomSheetDemo.routeName,
|
routeName: ModalBottomSheetDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ModalBottomSheetDemo()
|
buildRoute: (BuildContext context) => new ModalBottomSheetDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Over-scroll',
|
title: 'Over-scroll',
|
||||||
|
subtitle: 'Refresh and overscroll indicators',
|
||||||
routeName: OverscrollDemo.routeName,
|
routeName: OverscrollDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new OverscrollDemo()
|
buildRoute: (BuildContext context) => new OverscrollDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Page selector',
|
title: 'Page selector',
|
||||||
|
subtitle: 'A pageable list and other widgets',
|
||||||
routeName: PageSelectorDemo.routeName,
|
routeName: PageSelectorDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new PageSelectorDemo()
|
buildRoute: (BuildContext context) => new PageSelectorDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Persistent bottom sheet',
|
title: 'Persistent bottom sheet',
|
||||||
|
subtitle: 'A sheet that slides up from the bottom',
|
||||||
routeName: PersistentBottomSheetDemo.routeName,
|
routeName: PersistentBottomSheetDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new PersistentBottomSheetDemo()
|
buildRoute: (BuildContext context) => new PersistentBottomSheetDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Progress indicators',
|
title: 'Progress indicators',
|
||||||
|
subtitle: 'All kinds: linear, circular, indeterminate, etc',
|
||||||
routeName: ProgressIndicatorDemo.routeName,
|
routeName: ProgressIndicatorDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ProgressIndicatorDemo()
|
buildRoute: (BuildContext context) => new ProgressIndicatorDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Scrollable tabs',
|
title: 'Scrollable tabs',
|
||||||
|
subtitle: 'A tab bar that scrolls',
|
||||||
routeName: ScrollableTabsDemo.routeName,
|
routeName: ScrollableTabsDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ScrollableTabsDemo()
|
buildRoute: (BuildContext context) => new ScrollableTabsDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Selection controls',
|
title: 'Selection controls',
|
||||||
|
subtitle: 'Checkboxes, radio buttons, and switches',
|
||||||
routeName: SelectionControlsDemo.routeName,
|
routeName: SelectionControlsDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new SelectionControlsDemo()
|
buildRoute: (BuildContext context) => new SelectionControlsDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Sliders',
|
title: 'Sliders',
|
||||||
|
subtitle: 'Select a value by dragging the slider thumb',
|
||||||
routeName: SliderDemo.routeName,
|
routeName: SliderDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new SliderDemo()
|
buildRoute: (BuildContext context) => new SliderDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Snackbar',
|
title: 'Snackbar',
|
||||||
|
subtitle: 'Temporary message that appears at the bottom',
|
||||||
routeName: SnackBarDemo.routeName,
|
routeName: SnackBarDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new SnackBarDemo()
|
buildRoute: (BuildContext context) => new SnackBarDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Tabs',
|
title: 'Tabs',
|
||||||
|
subtitle: 'Tabs with indepdently scrollable views',
|
||||||
routeName: TabsDemo.routeName,
|
routeName: TabsDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new TabsDemo()
|
buildRoute: (BuildContext context) => new TabsDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Text fields',
|
title: 'Text fields',
|
||||||
|
subtitle: 'A Single line of editable text',
|
||||||
routeName: TextFieldDemo.routeName,
|
routeName: TextFieldDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new TextFieldDemo()
|
buildRoute: (BuildContext context) => new TextFieldDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Time picker',
|
title: 'Time picker',
|
||||||
|
subtitle: 'Choose a hours and minutes',
|
||||||
routeName: TimePickerDemo.routeName,
|
routeName: TimePickerDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new TimePickerDemo()
|
buildRoute: (BuildContext context) => new TimePickerDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Tooltips',
|
title: 'Tooltips',
|
||||||
|
subtitle: 'Display a short message on long-press',
|
||||||
routeName: TooltipDemo.routeName,
|
routeName: TooltipDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new TooltipDemo()
|
buildRoute: (BuildContext context) => new TooltipDemo()
|
||||||
),
|
),
|
||||||
// Styles
|
// Styles
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Colors',
|
title: 'Colors',
|
||||||
|
subtitle: 'All of the predefined colors',
|
||||||
category: 'Style',
|
category: 'Style',
|
||||||
routeName: ColorsDemo.routeName,
|
routeName: ColorsDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new ColorsDemo()
|
buildRoute: (BuildContext context) => new ColorsDemo()
|
||||||
),
|
),
|
||||||
new GalleryItem(
|
new GalleryItem(
|
||||||
title: 'Typography',
|
title: 'Typography',
|
||||||
|
subtitle: 'All of the predefined text styles',
|
||||||
category: 'Style',
|
category: 'Style',
|
||||||
routeName: TypographyDemo.routeName,
|
routeName: TypographyDemo.routeName,
|
||||||
buildRoute: (BuildContext context) => new TypographyDemo()
|
buildRoute: (BuildContext context) => new TypographyDemo()
|
||||||
|
@ -74,14 +74,6 @@ void main() {
|
|||||||
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
|
await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
|
||||||
await tester.pump(); // triggers a frame
|
await tester.pump(); // triggers a frame
|
||||||
|
|
||||||
// Expand the demo category submenus.
|
|
||||||
for (String category in demoCategories.reversed) {
|
|
||||||
await tester.tap(find.text(category));
|
|
||||||
await tester.pump();
|
|
||||||
await tester.pump(
|
|
||||||
const Duration(seconds: 1)); // Wait until the menu has expanded.
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<double> scrollDeltas = new List<double>();
|
final List<double> scrollDeltas = new List<double>();
|
||||||
double previousY = tester.getTopRight(find.text(demoCategories[0])).y;
|
double previousY = tester.getTopRight(find.text(demoCategories[0])).y;
|
||||||
for (String routeName in routeNames) {
|
for (String routeName in routeNames) {
|
||||||
@ -94,8 +86,7 @@ void main() {
|
|||||||
for (int i = 0; i < routeNames.length; i += 1) {
|
for (int i = 0; i < routeNames.length; i += 1) {
|
||||||
final String routeName = routeNames[i];
|
final String routeName = routeNames[i];
|
||||||
await smokeDemo(tester, routeName);
|
await smokeDemo(tester, routeName);
|
||||||
await tester.scroll(findGalleryItemByRouteName(tester, routeName),
|
await tester.scroll(findGalleryItemByRouteName(tester, routeName), new Offset(0.0, scrollDeltas[i]));
|
||||||
new Offset(0.0, scrollDeltas[i]));
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ final List<String> demoTitles = <String>[
|
|||||||
// Demos
|
// Demos
|
||||||
// 'Pesto', TODO(hansmuller): restore when Pesto has a back button.
|
// 'Pesto', TODO(hansmuller): restore when Pesto has a back button.
|
||||||
'Shrine',
|
'Shrine',
|
||||||
'Calculator',
|
|
||||||
'Contacts',
|
'Contacts',
|
||||||
// Components
|
// Components
|
||||||
'Buttons',
|
'Buttons',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user