implemented a central haptic feedback system with configurable levels (off, reduced, full), added a Haptics facade providing semantic feedback methods, integrated haptic cues across navigation, settings toggles, and async action results, and updated version to 1.1.0+54

This commit is contained in:
2026-05-30 13:54:19 +02:00
parent 01b4b44010
commit ece0669f7d
26 changed files with 308 additions and 75 deletions
+3
View File
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'haptics.dart';
/// Copies [text] to the system clipboard and shows a SnackBar.
Future<void> copyToClipboard(
BuildContext context,
@@ -8,6 +10,7 @@ Future<void> copyToClipboard(
String successMessage = 'In Zwischenablage kopiert',
}) async {
await Clipboard.setData(ClipboardData(text: text));
Haptics.selection();
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
+72
View File
@@ -0,0 +1,72 @@
import 'dart:async';
import 'package:flutter/services.dart';
import '../state/app/modules/settings/bloc/settings_cubit.dart';
import '../storage/haptic_settings.dart';
import '../storage/settings.dart';
/// App-weite Haptik-Fassade. Liest die aktive Stufe aus einem in-memory Cache,
/// der einmal an den SettingsCubit gebunden wird — damit sind die Aufrufe
/// kontextfrei (egal ob aus Widget oder Util).
///
/// Semantische Methoden statt roher Impacts, damit Call-Sites die Intention
/// transportieren, nicht die Stärke.
class Haptics {
static HapticLevel _level = HapticLevel.full;
static StreamSubscription<Settings>? _sub;
/// Einmal aus App-Root aufrufen, sobald der [SettingsCubit] verfügbar ist.
static void bind(SettingsCubit cubit) {
_level = cubit.state.hapticSettings.level;
_sub?.cancel();
_sub = cubit.stream.listen((s) => _level = s.hapticSettings.level);
}
// --- reduced + full ---
/// Long-Press, der ein Bottom-Sheet / Action-Menü öffnet.
static void longPress() {
if (_level == HapticLevel.off) return;
HapticFeedback.mediumImpact();
}
/// Bestätigen eines ConfirmDialogs (nicht das Abbrechen).
static void confirm() {
if (_level == HapticLevel.off) return;
HapticFeedback.mediumImpact();
}
/// Fehler einer Async-Aktion / Dialog-Fehler.
static void error() {
if (_level == HapticLevel.off) return;
HapticFeedback.mediumImpact();
}
/// Seltener Dopamin-Moment — z.B. erfolgreicher Login.
static void heavyAccent() {
if (_level == HapticLevel.off) return;
HapticFeedback.heavyImpact();
}
// --- nur full ---
/// Erfolg einer Async-Aktion (Senden, Reagieren, Speichern, …).
static void success() {
if (_level != HapticLevel.full) return;
HapticFeedback.lightImpact();
}
/// Pull-to-Refresh ausgelöst.
static void refresh() {
if (_level != HapticLevel.full) return;
HapticFeedback.selectionClick();
}
/// Tab-Wechsel, Toggle-Switch, Clipboard-Copy — alles, was eine
/// subtile Quittung verdient.
static void selection() {
if (_level != HapticLevel.full) return;
HapticFeedback.selectionClick();
}
}