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';
|
||||
@@ -6,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 {
|
||||
@@ -53,10 +55,10 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
scrolledUnderElevation: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
HapticIconButton(
|
||||
onPressed: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
final navigator = Navigator.of(context);
|
||||
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,14 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
if (!mounted) return;
|
||||
|
||||
if (success) {
|
||||
Navigator.pop(context, updatedGroup);
|
||||
await HapticFeedback.successNotification();
|
||||
if (mounted) {
|
||||
Navigator.pop(context, updatedGroup);
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
await HapticFeedback.errorNotification();
|
||||
}
|
||||
showSnackbar(
|
||||
message: widget.groupToEdit == null
|
||||
? loc.error_creating_group
|
||||
|
||||
@@ -12,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';
|
||||
@@ -65,7 +66,7 @@ class _GroupDetailViewState extends State<GroupDetailView> {
|
||||
appBar: AppBar(
|
||||
title: Text(loc.group_profile),
|
||||
actions: [
|
||||
IconButton(
|
||||
HapticIconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
showDialog<bool>(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
@@ -111,7 +112,10 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
padding: const EdgeInsets.only(bottom: 85),
|
||||
itemCount: filteredGroups.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
return GroupTile(
|
||||
group: filteredGroups[index],
|
||||
isHighlighted:
|
||||
selectedGroupId == filteredGroups[index].id,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (selectedGroupId != filteredGroups[index].id) {
|
||||
@@ -121,11 +125,6 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||
}
|
||||
});
|
||||
},
|
||||
child: GroupTile(
|
||||
group: filteredGroups[index],
|
||||
isHighlighted:
|
||||
selectedGroupId == filteredGroups[index].id,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_popup/flutter_popup.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
@@ -12,6 +13,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 +122,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;
|
||||
@@ -329,6 +331,12 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10),
|
||||
barrierColor: Colors.transparent,
|
||||
contentDecoration: CustomTheme.standardBoxDecoration,
|
||||
onBeforePopup: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
},
|
||||
onAfterPopup: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
},
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setPopupState) => SizedBox(
|
||||
width: 280,
|
||||
@@ -338,7 +346,8 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
children: List.generate(
|
||||
_rulesets.length,
|
||||
(index) => GestureDetector(
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
selectedRuleset = _rulesets[index].$1;
|
||||
});
|
||||
@@ -412,6 +421,12 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10),
|
||||
barrierColor: Colors.transparent,
|
||||
contentDecoration: CustomTheme.standardBoxDecoration,
|
||||
onBeforePopup: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
},
|
||||
onAfterPopup: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
},
|
||||
content: StatefulBuilder(
|
||||
builder: (context, setPopupState) => SizedBox(
|
||||
width: 150,
|
||||
@@ -421,7 +436,8 @@ class _CreateGameViewState extends State<CreateGameView> {
|
||||
children: List.generate(
|
||||
_colors.length,
|
||||
(index) => GestureDetector(
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
selectedColor = _colors[index].$1;
|
||||
});
|
||||
|
||||
@@ -172,7 +172,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
) ??
|
||||
false,
|
||||
);
|
||||
|
||||
selectedGroup = await Navigator.of(context).push(
|
||||
adaptivePageRoute(
|
||||
builder: (context) => ChooseGroupView(
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
@@ -9,6 +10,7 @@ import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/settings_view/licenses_view.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/tiles/settings_list_tile.dart';
|
||||
@@ -197,19 +199,23 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 40,
|
||||
spacing: 10,
|
||||
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.
|
||||
HapticIconButton(
|
||||
color: CustomTheme.textColor,
|
||||
icon: const Icon(Icons.language),
|
||||
onPressed: () async => {
|
||||
await HapticFeedback.lightImpact(),
|
||||
launchUrl(
|
||||
Uri.parse('https://liquid-dev.de'),
|
||||
),
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: const FaIcon(FontAwesomeIcons.github),
|
||||
onTap: () => {
|
||||
HapticIconButton(
|
||||
color: CustomTheme.textColor,
|
||||
icon: const FaIcon(FontAwesomeIcons.github),
|
||||
onPressed: () async => {
|
||||
await HapticFeedback.lightImpact(),
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
'https://github.com/liquiddevelopmentde',
|
||||
@@ -217,15 +223,19 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
),
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Icon(
|
||||
HapticIconButton(
|
||||
color: CustomTheme.textColor,
|
||||
icon: Icon(
|
||||
Platform.isIOS
|
||||
? CupertinoIcons.mail_solid
|
||||
: Icons.email,
|
||||
),
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse('mailto:hi@liquid-dev.de'),
|
||||
),
|
||||
onPressed: () async => {
|
||||
await HapticFeedback.lightImpact(),
|
||||
launchUrl(
|
||||
Uri.parse('mailto:hi@liquid-dev.de'),
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -266,21 +276,42 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
void showImportSnackBar({
|
||||
required BuildContext context,
|
||||
required ImportResult result,
|
||||
}) {
|
||||
}) async {
|
||||
final loc = AppLocalizations.of(context);
|
||||
switch (result) {
|
||||
case ImportResult.success:
|
||||
showSnackbar(context: context, message: loc.data_successfully_imported);
|
||||
await HapticFeedback.successNotification();
|
||||
if (context.mounted) {
|
||||
showSnackbar(
|
||||
context: context,
|
||||
message: loc.data_successfully_imported,
|
||||
);
|
||||
}
|
||||
case ImportResult.invalidSchema:
|
||||
showSnackbar(context: context, message: loc.invalid_schema);
|
||||
await HapticFeedback.errorNotification();
|
||||
if (context.mounted) {
|
||||
showSnackbar(context: context, message: loc.invalid_schema);
|
||||
}
|
||||
case ImportResult.fileReadError:
|
||||
showSnackbar(context: context, message: loc.error_reading_file);
|
||||
await HapticFeedback.errorNotification();
|
||||
if (context.mounted) {
|
||||
showSnackbar(context: context, message: loc.error_reading_file);
|
||||
}
|
||||
case ImportResult.canceled:
|
||||
showSnackbar(context: context, message: loc.import_canceled);
|
||||
await HapticFeedback.errorNotification();
|
||||
if (context.mounted) {
|
||||
showSnackbar(context: context, message: loc.import_canceled);
|
||||
}
|
||||
case ImportResult.formatException:
|
||||
showSnackbar(context: context, message: loc.format_exception);
|
||||
await HapticFeedback.errorNotification();
|
||||
if (context.mounted) {
|
||||
showSnackbar(context: context, message: loc.format_exception);
|
||||
}
|
||||
case ImportResult.unknownException:
|
||||
showSnackbar(context: context, message: loc.unknown_exception);
|
||||
await HapticFeedback.errorNotification();
|
||||
if (context.mounted) {
|
||||
showSnackbar(context: context, message: loc.unknown_exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,15 +322,27 @@ class _SettingsViewState extends State<SettingsView> {
|
||||
void showExportSnackBar({
|
||||
required BuildContext context,
|
||||
required ExportResult result,
|
||||
}) {
|
||||
}) async {
|
||||
final loc = AppLocalizations.of(context);
|
||||
switch (result) {
|
||||
case ExportResult.success:
|
||||
showSnackbar(context: context, message: loc.data_successfully_exported);
|
||||
await HapticFeedback.successNotification();
|
||||
if (context.mounted) {
|
||||
showSnackbar(
|
||||
context: context,
|
||||
message: loc.data_successfully_exported,
|
||||
);
|
||||
}
|
||||
case ExportResult.canceled:
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
|
||||
@@ -48,7 +49,12 @@ class CustomWidthButton extends StatelessWidget {
|
||||
)!;
|
||||
|
||||
return ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
onPressed: onPressed == null
|
||||
? null
|
||||
: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
onPressed!.call();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: textcolor,
|
||||
disabledForegroundColor: disabledTextColor,
|
||||
@@ -78,7 +84,12 @@ class CustomWidthButton extends StatelessWidget {
|
||||
: Color.lerp(CustomTheme.primaryColor, Colors.black, 0.5)!;
|
||||
|
||||
return OutlinedButton(
|
||||
onPressed: onPressed,
|
||||
onPressed: onPressed == null
|
||||
? null
|
||||
: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
onPressed!.call();
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: textcolor,
|
||||
disabledForegroundColor: disabledTextColor,
|
||||
@@ -110,7 +121,12 @@ class CustomWidthButton extends StatelessWidget {
|
||||
disabledBackgroundColor = Colors.transparent;
|
||||
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
onPressed: onPressed == null
|
||||
? null
|
||||
: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
onPressed!.call();
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: textcolor,
|
||||
disabledForegroundColor: disabledTextColor,
|
||||
|
||||
23
lib/presentation/widgets/buttons/haptic_back_button.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.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 HapticIconButton(
|
||||
icon: Icon(iconData),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
23
lib/presentation/widgets/buttons/haptic_close_button.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.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 HapticIconButton(
|
||||
icon: Icon(iconData),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
51
lib/presentation/widgets/buttons/haptic_icon_button.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HapticIconButton extends StatelessWidget {
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
Kannst du bitte noch bei den drei neuen Buttons den Splash-Effekt entfernen und eine Scale Animation hinzufügen, ähnlich wie bei main menu button? Ggf. ersetzt du dann Kannst du bitte noch bei den drei neuen Buttons den Splash-Effekt entfernen und eine Scale Animation hinzufügen, ähnlich wie bei main menu button? Ggf. ersetzt du dann `IconButton()` einfach durch einen `Container()`, vllt ist das sogar einfacher
sneeex
commented
du meinst bei allen IconButtons? weil das ist ja dann nicht nur die drei buttons in den settings, sondern alle back buttons auch. Und was für ein scale meinst du? icon scale? button scale insgesamt? und wieso? finde das eigentlich glaube nicht so geil du meinst bei allen IconButtons? weil das ist ja dann nicht nur die drei buttons in den settings, sondern alle back buttons auch. Und was für ein scale meinst du? icon scale? button scale insgesamt? und wieso? finde das eigentlich glaube nicht so geil
flixcoo
commented
Ja bei allen Icon Buttons. Ja bei allen Icon Buttons.
Meinetwegen auch ohne Scale Effekt, aber auf jeden Fall ohne Splash. Und mit Scale meinte ich dass das Icon sich in der Größe verändert, wie beim `MainMenuButton`
sneeex
commented
Beim Main Menu Button ist aber der ganze Button mit Scale und nicht nur das Icon > Ja bei allen Icon Buttons.
> Meinetwegen auch ohne Scale Effekt, aber auf jeden Fall ohne Splash. Und mit Scale meinte ich dass das Icon sich in der Größe verändert, wie beim `MainMenuButton`
Beim Main Menu Button ist aber der ganze Button mit Scale und nicht nur das Icon
flixcoo
commented
Der Icon Button ist ja aber nur n Icon, der hat ja keinen Hintergrund Der Icon Button ist ja aber nur n Icon, der hat ja keinen Hintergrund
|
||||
const HapticIconButton({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.onPressed,
|
||||
this.iconSize,
|
||||
this.color,
|
||||
this.padding,
|
||||
this.alignment,
|
||||
this.constraints,
|
||||
this.style,
|
||||
this.isSelected,
|
||||
this.selectedIcon,
|
||||
});
|
||||
|
||||
final Widget icon;
|
||||
final VoidCallback? onPressed;
|
||||
final double? iconSize;
|
||||
final Color? color;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final AlignmentGeometry? alignment;
|
||||
final BoxConstraints? constraints;
|
||||
final ButtonStyle? style;
|
||||
final bool? isSelected;
|
||||
final Widget? selectedIcon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
iconSize: iconSize,
|
||||
highlightColor: Colors.transparent, //disable splash animation
|
||||
color: color,
|
||||
padding: padding,
|
||||
|
sneeex marked this conversation as resolved
flixcoo
commented
Nicht sinnvoll, wir sind ja auf mobile, da gibts ja keinen hover state Nicht sinnvoll, wir sind ja auf mobile, da gibts ja keinen hover state
|
||||
alignment: alignment ?? Alignment.center,
|
||||
constraints: constraints,
|
||||
style: style,
|
||||
isSelected: isSelected,
|
||||
selectedIcon: selectedIcon,
|
||||
icon: icon,
|
||||
onPressed: onPressed == null
|
||||
? null
|
||||
: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
onPressed!.call();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class MainMenuButton extends StatefulWidget {
|
||||
/// A button for the main menu with an optional icon and a press animation.
|
||||
@@ -65,19 +66,27 @@ class _MainMenuButtonState extends State<MainMenuButton>
|
||||
onTapDown: (_) {
|
||||
_animationController.forward();
|
||||
if (widget.onLongPressed != null) {
|
||||
_longPressTimer = Timer(const Duration(milliseconds: 400), () {
|
||||
_isLongPressing = true;
|
||||
widget.onLongPressed?.call();
|
||||
_repeatTimer = Timer.periodic(
|
||||
const Duration(milliseconds: 250),
|
||||
(_) => widget.onLongPressed?.call(),
|
||||
);
|
||||
});
|
||||
_longPressTimer = Timer(
|
||||
const Duration(milliseconds: 400),
|
||||
() async {
|
||||
_isLongPressing = true;
|
||||
widget.onLongPressed?.call();
|
||||
await HapticFeedback.heavyImpact();
|
||||
_repeatTimer = Timer.periodic(
|
||||
const Duration(milliseconds: 250),
|
||||
(_) async {
|
||||
widget.onLongPressed?.call();
|
||||
await HapticFeedback.heavyImpact();
|
||||
},
|
||||
);
|
||||
},
|
||||
|
sneeex marked this conversation as resolved
Outdated
flixcoo
commented
Warum ist hier nur auf dem Short Tab eine Vibration und nicht auf dem Long Tap? Warum ist hier nur auf dem Short Tab eine Vibration und nicht auf dem Long Tap?
sneeex
commented
nicht gecheckt, habs geändert nicht gecheckt, habs geändert
|
||||
);
|
||||
}
|
||||
},
|
||||
onTapUp: (_) async {
|
||||
_cancelTimers();
|
||||
if (mounted && !_isLongPressing) {
|
||||
await HapticFeedback.selectionClick();
|
||||
widget.onPressed();
|
||||
}
|
||||
_isLongPressing = false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
||||
|
||||
@@ -26,7 +27,10 @@ class CustomDialogAction extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedDialogButton(
|
||||
onPressed: onPressed,
|
||||
onPressed: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
onPressed.call();
|
||||
},
|
||||
buttonText: text,
|
||||
buttonType: buttonType,
|
||||
isDescructive: isDestructive,
|
||||
|
||||
@@ -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';
|
||||
@@ -143,7 +144,8 @@ class _PlayerSelectionState extends State<PlayerSelection> {
|
||||
text: player.name,
|
||||
suffixText: getNameCountText(player),
|
||||
onIconTap: () {
|
||||
setState(() {
|
||||
setState(() async {
|
||||
await HapticFeedback.selectionClick();
|
||||
// Removes the player from the selection and notifies the parent.
|
||||
selectedPlayers.remove(player);
|
||||
widget.onChanged([...selectedPlayers]);
|
||||
@@ -197,7 +199,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 +297,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/custom_theme.dart';
|
||||
|
||||
class ChooseTile extends StatefulWidget {
|
||||
@@ -30,7 +31,14 @@ class _ChooseTileState extends State<ChooseTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onPressed,
|
||||
onTap: widget.onPressed != null
|
||||
? () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
if (widget.onPressed != null) {
|
||||
widget.onPressed!.call();
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Container(
|
||||
margin: CustomTheme.tileMargin,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
|
||||
@@ -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/core/enums.dart';
|
||||
@@ -53,8 +54,18 @@ class GameTile extends StatelessWidget {
|
||||
final gameColor = badgeColor ?? getColorFromGameColor(GameColor.orange);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
if (onTap != null) {
|
||||
onTap!.call();
|
||||
}
|
||||
},
|
||||
onLongPress: () async {
|
||||
await HapticFeedback.heavyImpact();
|
||||
if (onLongPress != null) {
|
||||
onLongPress!.call();
|
||||
}
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
decoration: !isHighlighted
|
||||
|
||||
@@ -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,12 @@ class _GroupTileState extends State<GroupTile> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
if (widget.onTap != null) {
|
||||
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';
|
||||
|
||||
class CustomCheckboxListTile extends StatelessWidget {
|
||||
@@ -16,7 +17,10 @@ class CustomCheckboxListTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(!value),
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
onChanged(!value);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
@@ -29,7 +33,8 @@ class CustomCheckboxListTile extends StatelessWidget {
|
||||
children: [
|
||||
Checkbox(
|
||||
value: value,
|
||||
onChanged: (bool? v) {
|
||||
onChanged: (bool? v) async {
|
||||
await HapticFeedback.selectionClick();
|
||||
if (v == null) return;
|
||||
onChanged(v);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:core' hide Match;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:tallee/core/common.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
@@ -51,7 +52,10 @@ class _MatchTileState extends State<MatchTile> {
|
||||
final loc = AppLocalizations.of(context);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
onTap: () async {
|
||||
await HapticFeedback.selectionClick();
|
||||
widget.onTap.call();
|
||||
},
|
||||
child: Container(
|
||||
margin: EdgeInsets.zero,
|
||||
width: widget.width,
|
||||
|
||||
@@ -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),
|
||||
|
||||
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