Haptisches Feedback hinzufügen #216
@@ -1,4 +1,6 @@
|
||||
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
|
||||
class CustomTheme {
|
||||
@@ -83,6 +85,11 @@ class CustomTheme {
|
||||
iconTheme: IconThemeData(color: textColor),
|
||||
);
|
||||
|
||||
static final ActionIconThemeData actionIconTheme = ActionIconThemeData(
|
||||
|
flixcoo marked this conversation as resolved
|
||||
backButtonIconBuilder: (context) => const HapticBackButton(),
|
||||
closeButtonIconBuilder: (context) => const HapticCloseButton(),
|
||||
);
|
||||
|
||||
static const SearchBarThemeData searchBarTheme = SearchBarThemeData(
|
||||
textStyle: WidgetStatePropertyAll(TextStyle(color: textColor)),
|
||||
hintStyle: WidgetStatePropertyAll(TextStyle(color: hintColor)),
|
||||
|
||||
@@ -42,6 +42,7 @@ class GameTracker extends StatelessWidget {
|
||||
scaffoldBackgroundColor: CustomTheme.backgroundColor,
|
||||
// themes
|
||||
appBarTheme: CustomTheme.appBarTheme,
|
||||
actionIconTheme: CustomTheme.actionIconTheme,
|
||||
inputDecorationTheme: CustomTheme.inputDecorationTheme,
|
||||
searchBarTheme: CustomTheme.searchBarTheme,
|
||||
radioTheme: CustomTheme.radioTheme,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
@@ -55,8 +56,9 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
final navigator = Navigator.of(context);
|
||||
await HapticFeedback.selectionClick();
|
||||
await navigator.push(
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
Warum rufst du hier extra nochmal Warum rufst du hier extra nochmal `HapticFeedback.selectionClick()` auf?
sneeex
commented
war wohl ausversehen, vmtl noch aus dem testing, habs entfernt war wohl ausversehen, vmtl noch aus dem testing, habs entfernt
|
||||
adaptivePageRoute(builder: (_) => const SettingsView()),
|
||||
);
|
||||
setState(() {
|
||||
@@ -125,7 +127,8 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
}
|
||||
|
||||
/// Handles tab tap events. Updates the current [index] state.
|
||||
void onTabTapped(int index) {
|
||||
void onTabTapped(int index) async {
|
||||
await HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
currentIndex = index;
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
@@ -133,8 +134,10 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
if (!mounted) return;
|
||||
|
||||
if (success) {
|
||||
await HapticFeedback.successNotification();
|
||||
Navigator.pop(context, updatedGroup);
|
||||
} else {
|
||||
await HapticFeedback.errorNotification();
|
||||
showSnackbar(
|
||||
message: widget.groupToEdit == null
|
||||
? loc.error_creating_group
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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';
|
||||
@@ -68,6 +69,7 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => CustomAlertDialog(
|
||||
@@ -75,11 +77,17 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
content: Text(loc.this_cannot_be_undone),
|
||||
actions: [
|
||||
CustomDialogAction(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
onPressed: () async {
|
||||
await HapticFeedback.warningNotification();
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
text: loc.delete,
|
||||
),
|
||||
CustomDialogAction(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
onPressed: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
buttonType: ButtonType.secondary,
|
||||
text: loc.cancel,
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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';
|
||||
@@ -102,6 +103,7 @@ class _GroupViewState extends State<GroupView> {
|
||||
text: loc.create_group,
|
||||
icon: Icons.group_add,
|
||||
onPressed: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
await Navigator.push(
|
||||
context,
|
||||
adaptivePageRoute(
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
@@ -201,7 +202,8 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: const Icon(Icons.language),
|
||||
onTap: () => {
|
||||
|
sneeex marked this conversation as resolved
Outdated
flixcoo
commented
Kann man diese drei Icon Buttons nicht direkt durch Kann man diese drei Icon Buttons nicht direkt durch `HapticIconButton` ersetzen?
sneeex
commented
Kann man, aber der hat doch nen ganz anderes Design oder nicht? Kann man, aber der hat doch nen ganz anderes Design oder nicht?
flixcoo
commented
Hab’s mir jetzt nicht genau angeschaut aber ist doch eigentlich auch nur n clickable Icon. Hab’s mir jetzt nicht genau angeschaut aber ist doch eigentlich auch nur n clickable Icon.
|
||||
onTap: () async => {
|
||||
await HapticFeedback.lightImpact(),
|
||||
launchUrl(
|
||||
Uri.parse('https://liquid-dev.de'),
|
||||
),
|
||||
@@ -209,7 +211,8 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
),
|
||||
GestureDetector(
|
||||
child: const FaIcon(FontAwesomeIcons.github),
|
||||
onTap: () => {
|
||||
onTap: () async => {
|
||||
await HapticFeedback.lightImpact(),
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
'https://github.com/liquiddevelopmentde',
|
||||
@@ -223,9 +226,12 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
? CupertinoIcons.mail_solid
|
||||
: Icons.email,
|
||||
),
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse('mailto:hi@liquid-dev.de'),
|
||||
),
|
||||
onTap: () async => {
|
||||
await HapticFeedback.lightImpact(),
|
||||
launchUrl(
|
||||
Uri.parse('mailto:hi@liquid-dev.de'),
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -266,20 +272,38 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
void showImportSnackBar({
|
||||
required BuildContext context,
|
||||
required ImportResult result,
|
||||
}) {
|
||||
}) async {
|
||||
final loc = AppLocalizations.of(context);
|
||||
switch (result) {
|
||||
case ImportResult.success:
|
||||
if (context.mounted) {
|
||||
await HapticFeedback.successNotification();
|
||||
}
|
||||
showSnackbar(context: context, message: loc.data_successfully_imported);
|
||||
case ImportResult.invalidSchema:
|
||||
if (context.mounted) {
|
||||
await HapticFeedback.errorNotification();
|
||||
}
|
||||
showSnackbar(context: context, message: loc.invalid_schema);
|
||||
case ImportResult.fileReadError:
|
||||
if (context.mounted) {
|
||||
await HapticFeedback.errorNotification();
|
||||
}
|
||||
showSnackbar(context: context, message: loc.error_reading_file);
|
||||
case ImportResult.canceled:
|
||||
if (context.mounted) {
|
||||
await HapticFeedback.errorNotification();
|
||||
}
|
||||
showSnackbar(context: context, message: loc.import_canceled);
|
||||
case ImportResult.formatException:
|
||||
if (context.mounted) {
|
||||
await HapticFeedback.errorNotification();
|
||||
}
|
||||
showSnackbar(context: context, message: loc.format_exception);
|
||||
case ImportResult.unknownException:
|
||||
if (context.mounted) {
|
||||
await HapticFeedback.errorNotification();
|
||||
}
|
||||
showSnackbar(context: context, message: loc.unknown_exception);
|
||||
}
|
||||
}
|
||||
@@ -291,13 +315,16 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
void showExportSnackBar({
|
||||
required BuildContext context,
|
||||
required ExportResult result,
|
||||
}) {
|
||||
}) async {
|
||||
final loc = AppLocalizations.of(context);
|
||||
switch (result) {
|
||||
case ExportResult.success:
|
||||
await HapticFeedback.successNotification();
|
||||
showSnackbar(context: context, message: loc.data_successfully_exported);
|
||||
case ExportResult.canceled:
|
||||
await HapticFeedback.errorNotification();
|
||||
showSnackbar(context: context, message: loc.export_canceled);
|
||||
await HapticFeedback.errorNotification();
|
||||
case ExportResult.unknownException:
|
||||
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/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
@@ -197,7 +198,8 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
text: suggestedPlayers[index].name,
|
||||
suffixText: getNameCountText(suggestedPlayers[index]),
|
||||
icon: Icons.add,
|
||||
onPressed: () {
|
||||
onPressed: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
// If the player is not already selected
|
||||
if (!selectedPlayers.contains(
|
||||
@@ -294,8 +296,10 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
|
||||
if (success) {
|
||||
_handleSuccessfulPlayerCreation(createdPlayer);
|
||||
await HapticFeedback.successNotification();
|
||||
showSnackBarMessage(loc.successfully_added_player(playerName));
|
||||
} else {
|
||||
await HapticFeedback.errorNotification();
|
||||
showSnackBarMessage(loc.could_not_add_player(playerName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/data/models/group.dart';
|
||||
@@ -33,7 +34,10 @@ class _GroupTileState extends State<GroupTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
widget.onTap?.call();
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
margin: CustomTheme.standardMargin,
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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/oss_licenses.dart';
|
||||
@@ -15,8 +16,10 @@ class LicenseTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
onTap: () async {
|
||||
final navigator = Navigator.of(context);
|
||||
await HapticFeedback.selectionClick();
|
||||
navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LicenseDetailView(package: package),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
||||
|
||||
@@ -36,7 +37,10 @@ class SettingsListTile extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.95,
|
||||
child: GestureDetector(
|
||||
onTap: onPressed ?? () {},
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
onPressed?.call();
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.zero,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
|
||||
Reference in New Issue
Block a user
Bitte überprüfen: So wie ich getestet haben kann man an diversen Stellen die Buttons weglassen, wenn sie nur navigation übernehmen. Bitte alle Buttons in den App Bars entfernen, wenn das tatsächlich möglich ist.
hab einmal per search alles mit appbar angeguckt, die buttons sind nur da explizit drin, wo sie auch gebraucht werden, weil sie z.B. mit einer variable den screen poppen
Alles klar, dann passt
aber grundsätzlich hast du recht, standardmäßig ist der zurück button immer da