feat: add haptic feedback to more user interactions
Some checks failed
Pull Request Pipeline / test (pull_request) Successful in 49s
Pull Request Pipeline / lint (pull_request) Failing after 50s

This commit is contained in:
2026-05-11 10:27:35 +02:00
parent 1d20127af4
commit bc59d1d91c
20 changed files with 165 additions and 53 deletions

View File

@@ -7,6 +7,7 @@ import 'package:tallee/presentation/views/main_menu/group_view/group_view.dart';
import 'package:tallee/presentation/views/main_menu/match_view/match_view.dart';
import 'package:tallee/presentation/views/main_menu/settings_view/settings_view.dart';
import 'package:tallee/presentation/views/main_menu/statistics_view.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
import 'package:tallee/presentation/widgets/navbar_item.dart';
class CustomNavigationBar extends StatefulWidget {
@@ -54,7 +55,7 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
backgroundColor: CustomTheme.backgroundColor,
scrolledUnderElevation: 0,
actions: [
IconButton(
HapticIconButton(
onPressed: () async {
final navigator = Navigator.of(context);
await HapticFeedback.selectionClick();

View File

@@ -135,9 +135,13 @@ class _CreateGroupViewState extends State<CreateGroupView> {
if (success) {
await HapticFeedback.successNotification();
Navigator.pop(context, updatedGroup);
if (mounted) {
Navigator.pop(context, updatedGroup);
}
} else {
await HapticFeedback.errorNotification();
if (mounted) {
await HapticFeedback.errorNotification();
}
showSnackbar(
message: widget.groupToEdit == null
? loc.error_creating_group

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:tallee/core/adaptive_page_route.dart';
@@ -13,6 +12,7 @@ import 'package:tallee/data/models/player.dart';
import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart';
import 'package:tallee/presentation/widgets/app_skeleton.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart';
@@ -66,10 +66,9 @@ class _GroupDetailViewState extends State<GroupDetailView> {
appBar: AppBar(
title: Text(loc.group_profile),
actions: [
IconButton(
HapticIconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await HapticFeedback.selectionClick();
showDialog<bool>(
context: context,
builder: (context) => CustomAlertDialog(
@@ -77,17 +76,11 @@ class _GroupDetailViewState extends State<GroupDetailView> {
content: Text(loc.this_cannot_be_undone),
actions: [
CustomDialogAction(
onPressed: () async {
await HapticFeedback.warningNotification();
Navigator.of(context).pop(true);
},
onPressed: () => Navigator.of(context).pop(true),
text: loc.delete,
),
CustomDialogAction(
onPressed: () async {
await HapticFeedback.selectionClick();
Navigator.of(context).pop(false);
},
onPressed: () => Navigator.of(context).pop(false),
buttonType: ButtonType.secondary,
text: loc.cancel,
),

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:tallee/core/adaptive_page_route.dart';
import 'package:tallee/core/constants.dart';
@@ -103,7 +102,6 @@ class _GroupViewState extends State<GroupView> {
text: loc.create_group,
icon: Icons.group_add,
onPressed: () async {
await HapticFeedback.selectionClick();
await Navigator.push(
context,
adaptivePageRoute(

View File

@@ -7,6 +7,7 @@ import 'package:tallee/data/db/database.dart';
import 'package:tallee/data/models/game.dart';
import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_game_view.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
import 'package:tallee/presentation/widgets/tiles/game_tile.dart';
import 'package:tallee/presentation/widgets/top_centered_message.dart';
@@ -70,7 +71,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
backgroundColor: CustomTheme.backgroundColor,
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: IconButton(
leading: HapticIconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop(
@@ -83,7 +84,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
},
),
actions: [
IconButton(
HapticIconButton(
icon: const Icon(Icons.add),
onPressed: () async {
final result = await Navigator.push(

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:tallee/core/custom_theme.dart';
import 'package:tallee/data/models/group.dart';
import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
import 'package:tallee/presentation/widgets/tiles/group_tile.dart';
import 'package:tallee/presentation/widgets/top_centered_message.dart';
@@ -45,7 +46,7 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
backgroundColor: CustomTheme.backgroundColor,
resizeToAvoidBottomInset: false,
appBar: AppBar(
leading: IconButton(
leading: HapticIconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop(

View File

@@ -12,6 +12,7 @@ import 'package:tallee/data/models/game.dart';
import 'package:tallee/data/models/group.dart';
import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart';
import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart';
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
@@ -120,7 +121,7 @@ class _CreateGameViewState extends State<CreateGameView> {
title: Text(isEditing ? loc.edit_game : loc.create_game),
actions: [
if (isEditMode())
IconButton(
HapticIconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
if (!context.mounted) return;

View File

@@ -11,6 +11,7 @@ import 'package:tallee/data/models/match.dart';
import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart';
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart';
@@ -60,7 +61,7 @@ class _MatchDetailViewState extends State<MatchDetailView> {
appBar: AppBar(
title: Text(loc.match_profile),
actions: [
IconButton(
HapticIconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
showDialog<bool>(

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:tallee/core/custom_theme.dart';
import 'package:tallee/core/enums.dart';
@@ -8,6 +9,7 @@ import 'package:tallee/data/models/player.dart';
import 'package:tallee/data/models/score_entry.dart';
import 'package:tallee/l10n/generated/app_localizations.dart';
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart';
import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_radio_list_tile.dart';
import 'package:tallee/presentation/widgets/tiles/match_result_view/live_edit_list_tile.dart';
@@ -112,7 +114,7 @@ class _MatchResultViewState extends State<MatchResultView> {
return Scaffold(
backgroundColor: CustomTheme.backgroundColor,
appBar: AppBar(
leading: IconButton(
leading: HapticIconButton(
icon: const Icon(Icons.close),
onPressed: () {
widget.onWinnerChanged?.call();
@@ -204,6 +206,7 @@ class _MatchResultViewState extends State<MatchResultView> {
: RadioGroup<Player>(
groupValue: _selectedPlayer,
onChanged: (Player? value) async {
await HapticFeedback.selectionClick();
setState(() {
_selectedPlayer = value;
});
@@ -217,6 +220,7 @@ class _MatchResultViewState extends State<MatchResultView> {
text: allPlayers[index].name,
value: allPlayers[index],
onContainerTap: (value) async {
await HapticFeedback.selectionClick();
setState(() {
// Check if the already selected player is the same as the newly tapped player.
if (_selectedPlayer == value) {
@@ -338,6 +342,12 @@ class _MatchResultViewState extends State<MatchResultView> {
},
);
},
onReorderStart: (int n) async {
await HapticFeedback.selectionClick();
},
onReorderEnd: (int n) async {
await HapticFeedback.selectionClick();
},
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {

View File

@@ -276,35 +276,38 @@ class _SettingsViewState extends State<SettingsView> {
final loc = AppLocalizations.of(context);
switch (result) {
case ImportResult.success:
await HapticFeedback.successNotification();
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:
await HapticFeedback.errorNotification();
if (context.mounted) {
await HapticFeedback.errorNotification();
showSnackbar(context: context, message: loc.invalid_schema);
}
showSnackbar(context: context, message: loc.invalid_schema);
case ImportResult.fileReadError:
await HapticFeedback.errorNotification();
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:
await HapticFeedback.errorNotification();
if (context.mounted) {
await HapticFeedback.errorNotification();
showSnackbar(context: context, message: loc.import_canceled);
}
showSnackbar(context: context, message: loc.import_canceled);
case ImportResult.formatException:
await HapticFeedback.errorNotification();
if (context.mounted) {
await HapticFeedback.errorNotification();
showSnackbar(context: context, message: loc.format_exception);
}
showSnackbar(context: context, message: loc.format_exception);
case ImportResult.unknownException:
await HapticFeedback.errorNotification();
if (context.mounted) {
await HapticFeedback.errorNotification();
showSnackbar(context: context, message: loc.unknown_exception);
}
showSnackbar(context: context, message: loc.unknown_exception);
}
}
@@ -320,13 +323,22 @@ class _SettingsViewState extends State<SettingsView> {
switch (result) {
case ExportResult.success:
await HapticFeedback.successNotification();
showSnackbar(context: context, message: loc.data_successfully_exported);
if (context.mounted) {
showSnackbar(
context: context,
message: loc.data_successfully_exported,
);
}
case ExportResult.canceled:
await HapticFeedback.errorNotification();
showSnackbar(context: context, message: loc.export_canceled);
await HapticFeedback.errorNotification();
if (context.mounted) {
showSnackbar(context: context, message: loc.export_canceled);
}
case ExportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
await HapticFeedback.errorNotification();
if (context.mounted) {
showSnackbar(context: context, message: loc.unknown_exception);
}
}
}