feat: add haptic feedback for various user interactions
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/haptic_back_button.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/haptic_close_button.dart';
|
||||||
|
|
||||||
/// Theme class that defines colors, border radius, padding, and decorations
|
/// Theme class that defines colors, border radius, padding, and decorations
|
||||||
class CustomTheme {
|
class CustomTheme {
|
||||||
@@ -83,6 +85,11 @@ class CustomTheme {
|
|||||||
iconTheme: IconThemeData(color: textColor),
|
iconTheme: IconThemeData(color: textColor),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static final ActionIconThemeData actionIconTheme = ActionIconThemeData(
|
||||||
|
backButtonIconBuilder: (context) => const HapticBackButton(),
|
||||||
|
closeButtonIconBuilder: (context) => const HapticCloseButton(),
|
||||||
|
);
|
||||||
|
|
||||||
static const SearchBarThemeData searchBarTheme = SearchBarThemeData(
|
static const SearchBarThemeData searchBarTheme = SearchBarThemeData(
|
||||||
textStyle: WidgetStatePropertyAll(TextStyle(color: textColor)),
|
textStyle: WidgetStatePropertyAll(TextStyle(color: textColor)),
|
||||||
hintStyle: WidgetStatePropertyAll(TextStyle(color: hintColor)),
|
hintStyle: WidgetStatePropertyAll(TextStyle(color: hintColor)),
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class GameTracker extends StatelessWidget {
|
|||||||
scaffoldBackgroundColor: CustomTheme.backgroundColor,
|
scaffoldBackgroundColor: CustomTheme.backgroundColor,
|
||||||
// themes
|
// themes
|
||||||
appBarTheme: CustomTheme.appBarTheme,
|
appBarTheme: CustomTheme.appBarTheme,
|
||||||
|
actionIconTheme: CustomTheme.actionIconTheme,
|
||||||
inputDecorationTheme: CustomTheme.inputDecorationTheme,
|
inputDecorationTheme: CustomTheme.inputDecorationTheme,
|
||||||
searchBarTheme: CustomTheme.searchBarTheme,
|
searchBarTheme: CustomTheme.searchBarTheme,
|
||||||
radioTheme: CustomTheme.radioTheme,
|
radioTheme: CustomTheme.radioTheme,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:tallee/core/adaptive_page_route.dart';
|
import 'package:tallee/core/adaptive_page_route.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||||
@@ -55,8 +56,9 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
|||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await Navigator.push(
|
final navigator = Navigator.of(context);
|
||||||
context,
|
await HapticFeedback.selectionClick();
|
||||||
|
await navigator.push(
|
||||||
adaptivePageRoute(builder: (_) => const SettingsView()),
|
adaptivePageRoute(builder: (_) => const SettingsView()),
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -125,7 +127,8 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handles tab tap events. Updates the current [index] state.
|
/// Handles tab tap events. Updates the current [index] state.
|
||||||
void onTabTapped(int index) {
|
void onTabTapped(int index) async {
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
setState(() {
|
setState(() {
|
||||||
currentIndex = index;
|
currentIndex = index;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tallee/core/constants.dart';
|
import 'package:tallee/core/constants.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
@@ -133,8 +134,10 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
await HapticFeedback.successNotification();
|
||||||
Navigator.pop(context, updatedGroup);
|
Navigator.pop(context, updatedGroup);
|
||||||
} else {
|
} else {
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
showSnackbar(
|
showSnackbar(
|
||||||
message: widget.groupToEdit == null
|
message: widget.groupToEdit == null
|
||||||
? loc.error_creating_group
|
? loc.error_creating_group
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tallee/core/adaptive_page_route.dart';
|
import 'package:tallee/core/adaptive_page_route.dart';
|
||||||
@@ -68,6 +69,7 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
showDialog<bool>(
|
showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CustomAlertDialog(
|
builder: (context) => CustomAlertDialog(
|
||||||
@@ -75,11 +77,17 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
|||||||
content: Text(loc.this_cannot_be_undone),
|
content: Text(loc.this_cannot_be_undone),
|
||||||
actions: [
|
actions: [
|
||||||
CustomDialogAction(
|
CustomDialogAction(
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () async {
|
||||||
|
await HapticFeedback.warningNotification();
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
text: loc.delete,
|
text: loc.delete,
|
||||||
),
|
),
|
||||||
CustomDialogAction(
|
CustomDialogAction(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () async {
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
|
Navigator.of(context).pop(false);
|
||||||
|
},
|
||||||
buttonType: ButtonType.secondary,
|
buttonType: ButtonType.secondary,
|
||||||
text: loc.cancel,
|
text: loc.cancel,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tallee/core/adaptive_page_route.dart';
|
import 'package:tallee/core/adaptive_page_route.dart';
|
||||||
import 'package:tallee/core/constants.dart';
|
import 'package:tallee/core/constants.dart';
|
||||||
@@ -102,6 +103,7 @@ class _GroupViewState extends State<GroupView> {
|
|||||||
text: loc.create_group,
|
text: loc.create_group,
|
||||||
icon: Icons.group_add,
|
icon: Icons.group_add,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
adaptivePageRoute(
|
adaptivePageRoute(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
@@ -201,7 +202,8 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: const Icon(Icons.language),
|
child: const Icon(Icons.language),
|
||||||
onTap: () => {
|
onTap: () async => {
|
||||||
|
await HapticFeedback.lightImpact(),
|
||||||
launchUrl(
|
launchUrl(
|
||||||
Uri.parse('https://liquid-dev.de'),
|
Uri.parse('https://liquid-dev.de'),
|
||||||
),
|
),
|
||||||
@@ -209,7 +211,8 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: const FaIcon(FontAwesomeIcons.github),
|
child: const FaIcon(FontAwesomeIcons.github),
|
||||||
onTap: () => {
|
onTap: () async => {
|
||||||
|
await HapticFeedback.lightImpact(),
|
||||||
launchUrl(
|
launchUrl(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
'https://github.com/liquiddevelopmentde',
|
'https://github.com/liquiddevelopmentde',
|
||||||
@@ -223,9 +226,12 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
? CupertinoIcons.mail_solid
|
? CupertinoIcons.mail_solid
|
||||||
: Icons.email,
|
: Icons.email,
|
||||||
),
|
),
|
||||||
onTap: () => launchUrl(
|
onTap: () async => {
|
||||||
|
await HapticFeedback.lightImpact(),
|
||||||
|
launchUrl(
|
||||||
Uri.parse('mailto:hi@liquid-dev.de'),
|
Uri.parse('mailto:hi@liquid-dev.de'),
|
||||||
),
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -266,20 +272,38 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
void showImportSnackBar({
|
void showImportSnackBar({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required ImportResult result,
|
required ImportResult result,
|
||||||
}) {
|
}) async {
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case ImportResult.success:
|
case ImportResult.success:
|
||||||
|
if (context.mounted) {
|
||||||
|
await HapticFeedback.successNotification();
|
||||||
|
}
|
||||||
showSnackbar(context: context, message: loc.data_successfully_imported);
|
showSnackbar(context: context, message: loc.data_successfully_imported);
|
||||||
case ImportResult.invalidSchema:
|
case ImportResult.invalidSchema:
|
||||||
|
if (context.mounted) {
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
|
}
|
||||||
showSnackbar(context: context, message: loc.invalid_schema);
|
showSnackbar(context: context, message: loc.invalid_schema);
|
||||||
case ImportResult.fileReadError:
|
case ImportResult.fileReadError:
|
||||||
|
if (context.mounted) {
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
|
}
|
||||||
showSnackbar(context: context, message: loc.error_reading_file);
|
showSnackbar(context: context, message: loc.error_reading_file);
|
||||||
case ImportResult.canceled:
|
case ImportResult.canceled:
|
||||||
|
if (context.mounted) {
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
|
}
|
||||||
showSnackbar(context: context, message: loc.import_canceled);
|
showSnackbar(context: context, message: loc.import_canceled);
|
||||||
case ImportResult.formatException:
|
case ImportResult.formatException:
|
||||||
|
if (context.mounted) {
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
|
}
|
||||||
showSnackbar(context: context, message: loc.format_exception);
|
showSnackbar(context: context, message: loc.format_exception);
|
||||||
case ImportResult.unknownException:
|
case ImportResult.unknownException:
|
||||||
|
if (context.mounted) {
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
|
}
|
||||||
showSnackbar(context: context, message: loc.unknown_exception);
|
showSnackbar(context: context, message: loc.unknown_exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,13 +315,16 @@ class _SettingsViewState extends State<SettingsView> {
|
|||||||
void showExportSnackBar({
|
void showExportSnackBar({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required ExportResult result,
|
required ExportResult result,
|
||||||
}) {
|
}) async {
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case ExportResult.success:
|
case ExportResult.success:
|
||||||
|
await HapticFeedback.successNotification();
|
||||||
showSnackbar(context: context, message: loc.data_successfully_exported);
|
showSnackbar(context: context, message: loc.data_successfully_exported);
|
||||||
case ExportResult.canceled:
|
case ExportResult.canceled:
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
showSnackbar(context: context, message: loc.export_canceled);
|
showSnackbar(context: context, message: loc.export_canceled);
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
case ExportResult.unknownException:
|
case ExportResult.unknownException:
|
||||||
showSnackbar(context: context, message: loc.unknown_exception);
|
showSnackbar(context: context, message: loc.unknown_exception);
|
||||||
}
|
}
|
||||||
|
|||||||
24
lib/presentation/widgets/buttons/haptic_back_button.dart
Normal file
24
lib/presentation/widgets/buttons/haptic_back_button.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class HapticBackButton extends StatelessWidget {
|
||||||
|
const HapticBackButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final iconData = switch (defaultTargetPlatform) {
|
||||||
|
TargetPlatform.iOS ||
|
||||||
|
TargetPlatform.macOS => Icons.arrow_back_ios_new_rounded,
|
||||||
|
_ => Icons.arrow_back_rounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(iconData),
|
||||||
|
onPressed: () async {
|
||||||
|
await HapticFeedback.mediumImpact();
|
||||||
|
Navigator.of(context).maybePop();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
lib/presentation/widgets/buttons/haptic_close_button.dart
Normal file
24
lib/presentation/widgets/buttons/haptic_close_button.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class HapticCloseButton extends StatelessWidget {
|
||||||
|
const HapticCloseButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final iconData = switch (defaultTargetPlatform) {
|
||||||
|
TargetPlatform.iOS || TargetPlatform.macOS => CupertinoIcons.xmark,
|
||||||
|
_ => Icons.close_rounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(iconData),
|
||||||
|
onPressed: () async {
|
||||||
|
await HapticFeedback.mediumImpact();
|
||||||
|
Navigator.of(context).maybePop();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tallee/core/common.dart';
|
import 'package:tallee/core/common.dart';
|
||||||
import 'package:tallee/core/constants.dart';
|
import 'package:tallee/core/constants.dart';
|
||||||
@@ -197,7 +198,8 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
text: suggestedPlayers[index].name,
|
text: suggestedPlayers[index].name,
|
||||||
suffixText: getNameCountText(suggestedPlayers[index]),
|
suffixText: getNameCountText(suggestedPlayers[index]),
|
||||||
icon: Icons.add,
|
icon: Icons.add,
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
setState(() {
|
setState(() {
|
||||||
// If the player is not already selected
|
// If the player is not already selected
|
||||||
if (!selectedPlayers.contains(
|
if (!selectedPlayers.contains(
|
||||||
@@ -294,8 +296,10 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
_handleSuccessfulPlayerCreation(createdPlayer);
|
_handleSuccessfulPlayerCreation(createdPlayer);
|
||||||
|
await HapticFeedback.successNotification();
|
||||||
showSnackBarMessage(loc.successfully_added_player(playerName));
|
showSnackBarMessage(loc.successfully_added_player(playerName));
|
||||||
} else {
|
} else {
|
||||||
|
await HapticFeedback.errorNotification();
|
||||||
showSnackBarMessage(loc.could_not_add_player(playerName));
|
showSnackBarMessage(loc.could_not_add_player(playerName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:tallee/core/common.dart';
|
import 'package:tallee/core/common.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
import 'package:tallee/data/models/group.dart';
|
import 'package:tallee/data/models/group.dart';
|
||||||
@@ -33,7 +34,10 @@ class _GroupTileState extends State<GroupTile> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: widget.onTap,
|
onTap: () async {
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
|
widget.onTap?.call();
|
||||||
|
},
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
margin: CustomTheme.standardMargin,
|
margin: CustomTheme.standardMargin,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart';
|
import 'package:tallee/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart';
|
||||||
import 'package:tallee/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart';
|
import 'package:tallee/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart';
|
||||||
@@ -15,8 +16,10 @@ class LicenseTile extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Navigator.of(context).push(
|
final navigator = Navigator.of(context);
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
|
navigator.push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => LicenseDetailView(package: package),
|
builder: (context) => LicenseDetailView(package: package),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:tallee/core/custom_theme.dart';
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
||||||
|
|
||||||
@@ -36,7 +37,10 @@ class SettingsListTile extends StatelessWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.95,
|
width: MediaQuery.of(context).size.width * 0.95,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: onPressed ?? () {},
|
onTap: () async {
|
||||||
|
await HapticFeedback.selectionClick();
|
||||||
|
onPressed?.call();
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
|
|||||||
Reference in New Issue
Block a user