v2.56 — UI/Design-Rework: Melo Dark Fusion (neue Startseite, 2-stufige Einstellungen, Design-System)
- Design-System: MeloDesignTokens ThemeExtension, Outfit-Font, Farben #0B0B10/#14141C/#1C1C26 - Akzentfarbe-User-Einstellung behalten (MeloTheme.akzentNotifier), Gradient aus Akzent - Startseite: Hero-Weiterhören-Karte mit Cover, Titel, Glow-Play-Button - 2-stufige Einstellungen: Standard + Expertenmodus (SharedPreferences Key 'experten_modus') - Komponenten: Pill-Buttons (radius 999), Card-Radius 20, Glow-Shadow, PressScale-Widget - Login: Gradient-Button mit Akzent-Glow - 199 Tests grün (194 bestehend + 5 neu): ThemeExtension, Expertenmodus, Akzent, PressScale, Hero - flutter analyze lib/ + test/ = 0 Issues - KEIN APK
This commit is contained in:
+233
-145
@@ -13,10 +13,8 @@ import 'cloud_screen.dart';
|
||||
import 'recap_screen.dart';
|
||||
import 'erweitert_screen.dart';
|
||||
|
||||
/// Vollwertiger Einstellungen-Screen – kein Popup mehr.
|
||||
/// Sprint D F4: alle Einstellungen funktionierend + persistiert
|
||||
/// (Auto-Sync-Intervall, Sleep-Timer-Standard, Wiedergabegeschwindigkeit,
|
||||
/// Akzentfarbe pro User, Speicher-Info).
|
||||
/// Einstellungen – 2-stufig: Standard + Expertenmodus (ab v2.56).
|
||||
/// Sprint D F4: alle Einstellungen funktionierend + persistiert.
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
||||
@@ -25,14 +23,19 @@ class SettingsScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
// ─── Einstellungen (aus SharedPreferences geladen) ───
|
||||
// ─── Standard-Einstellungen ───
|
||||
String _syncModus = 'realtime'; // 'realtime' | 'manual' | 'interval'
|
||||
int _cloudIntervallStunden = 48; // 48/72/120/168/336
|
||||
int _sleepDefaultMin = 0; // 0 = Aus
|
||||
int _cloudIntervallStunden = 48;
|
||||
int _sleepDefaultMin = 0;
|
||||
double _geschwindigkeit = 1.0;
|
||||
Color _akzent = MeloTheme.akzent;
|
||||
Map<String, int> _speicherInfo = const {'anzahl': 0, 'bytes': 0};
|
||||
|
||||
// ─── Expertenmodus ───
|
||||
bool _expertenModus = false;
|
||||
String? _letzterSync;
|
||||
String? _verbindungsStatus;
|
||||
|
||||
/// Verfügbare Zeit-Intervalle für Modus 3 (in Stunden).
|
||||
static const _intervallOptionen = {
|
||||
48: '2 Tage',
|
||||
@@ -62,13 +65,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
Future<void> _ladeEinstellungen() async {
|
||||
final p = await SharedPreferences.getInstance();
|
||||
final speed = p.getDouble('playback_speed') ?? 1.0;
|
||||
final akzentHex =
|
||||
p.getString(UserEffekt.akzentKey(AuthService().benutzer));
|
||||
// Sync-Modus laden (mit Migration von altem cloud_interval)
|
||||
final akzentHex = p.getString(UserEffekt.akzentKey(AuthService().benutzer));
|
||||
final modus = p.getString('sync_modus') ?? 'realtime';
|
||||
int intervallStunden = p.getInt('cloud_interval_stunden') ?? 0;
|
||||
if (intervallStunden <= 0) {
|
||||
// Migration: alter cloud_interval (1/3/6/12h) → 48h default
|
||||
final alt = p.getInt('cloud_interval') ?? 0;
|
||||
if (alt == 1 || alt == 3 || alt == 6 || alt == 12) {
|
||||
intervallStunden = 48;
|
||||
@@ -77,10 +77,17 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
intervallStunden = 48;
|
||||
}
|
||||
}
|
||||
// Prüfe, ob der Modus gültig ist
|
||||
if (!['realtime', 'manual', 'interval'].contains(modus)) {
|
||||
await p.setString('sync_modus', 'realtime');
|
||||
}
|
||||
|
||||
// Expertenmodus laden (Key: 'experten_modus')
|
||||
final experte = p.getBool('experten_modus') ?? false;
|
||||
|
||||
// Sync-Info für erweiterte Sektion
|
||||
final letzterSyncStr = p.getString('letzter_sync');
|
||||
final syncStat = CloudService().status;
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_syncModus = modus;
|
||||
@@ -88,8 +95,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_sleepDefaultMin = p.getInt('sleep_timer_default_min') ?? 0;
|
||||
_geschwindigkeit = speed;
|
||||
_akzent = UserEffekt.farbeAusHex(akzentHex) ?? MeloTheme.akzent;
|
||||
_expertenModus = experte;
|
||||
_letzterSync = letzterSyncStr;
|
||||
_verbindungsStatus = syncStat == CloudStatus.verbunden ? 'Verbunden' : 'Getrennt';
|
||||
});
|
||||
// Speed-Default auch auf den Player anwenden (gilt fürs Abspielen)
|
||||
await PlayerService().setGeschwindigkeit(speed);
|
||||
}
|
||||
|
||||
@@ -99,30 +108,24 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
setState(() => _speicherInfo = info);
|
||||
}
|
||||
|
||||
// ─── Setter (persistieren + anwenden) ───
|
||||
// ─── Setter ────────────────────────────────────────
|
||||
|
||||
/// Setzt den Sync-Modus und persistiert ihn.
|
||||
/// Startet/stoppt den Realtime-Service bzw. den Timer entsprechend.
|
||||
Future<void> _syncModusSetzen(String modus) async {
|
||||
setState(() => _syncModus = modus);
|
||||
final p = await SharedPreferences.getInstance();
|
||||
await p.setString('sync_modus', modus);
|
||||
|
||||
// Realtime-Service entsprechend starten/stoppen
|
||||
final rs = RealtimeSyncService();
|
||||
switch (modus) {
|
||||
case 'realtime':
|
||||
// SSE sofort starten
|
||||
await rs.starteWennAktiviert();
|
||||
break;
|
||||
case 'manual':
|
||||
// Kein Auto-Sync — SSE stoppen, Timer deaktivieren
|
||||
rs.stoppe();
|
||||
await p.setInt('cloud_interval', 0); // alten Timer deaktivieren
|
||||
await p.setInt('cloud_interval', 0);
|
||||
await SyncService.starteAutoSyncTimer();
|
||||
break;
|
||||
case 'interval':
|
||||
// Intervall-Timer — SSE stoppen, Timer mit cloud_interval_stunden starten
|
||||
rs.stoppe();
|
||||
await p.setInt('cloud_interval', _cloudIntervallStunden);
|
||||
await SyncService.starteAutoSyncTimer();
|
||||
@@ -130,12 +133,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Setzt das Cloud-Intervall (nur für Modus 3: interval).
|
||||
Future<void> _intervallSetzen(int stunden) async {
|
||||
setState(() => _cloudIntervallStunden = stunden);
|
||||
final p = await SharedPreferences.getInstance();
|
||||
await p.setInt('cloud_interval_stunden', stunden);
|
||||
// Timer neu starten (der cloud_interval-Wert wird aktualisiert)
|
||||
await p.setInt('cloud_interval', stunden);
|
||||
await SyncService.starteAutoSyncTimer();
|
||||
}
|
||||
@@ -155,12 +156,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
|
||||
Future<void> _akzentSetzen(Color farbe) async {
|
||||
setState(() => _akzent = farbe);
|
||||
MeloTheme.akzent = farbe; // live — main.dart rebuildet das Theme
|
||||
MeloTheme.akzent = farbe;
|
||||
final p = await SharedPreferences.getInstance();
|
||||
await p.setString(
|
||||
UserEffekt.akzentKey(AuthService().benutzer), _hexVonFarbe(farbe));
|
||||
}
|
||||
|
||||
Future<void> _expertenModusSetzen(bool wert) async {
|
||||
setState(() => _expertenModus = wert);
|
||||
final p = await SharedPreferences.getInstance();
|
||||
await p.setBool('experten_modus', wert);
|
||||
}
|
||||
|
||||
String _hexVonFarbe(Color c) {
|
||||
String hx(int w) => w.toRadixString(16).padLeft(2, '0').toUpperCase();
|
||||
return '${hx((c.r * 255).round())}${hx((c.g * 255).round())}${hx((c.b * 255).round())}';
|
||||
@@ -180,16 +187,16 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
return Scaffold(
|
||||
backgroundColor: MeloTheme.schwarz,
|
||||
appBar: AppBar(
|
||||
backgroundColor: MeloTheme.dunkel1,
|
||||
backgroundColor: MeloTheme.oberflaeche,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.white, size: 22),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: const Row(
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.settings, color: MeloTheme.rot, size: 20),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
Icon(Icons.settings, color: MeloTheme.akzent, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'Einstellungen',
|
||||
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600),
|
||||
),
|
||||
@@ -199,8 +206,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
children: [
|
||||
// ─── Sektion: Verbindung ───
|
||||
_sektionHeader('Verbindung'),
|
||||
// ═══ Sektion: Standard ═══
|
||||
_sektionHeader('⚙️ Standard', icon: Icons.tune),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// ─── Verbindung ───
|
||||
_einstellungsKachel(
|
||||
icon: Icons.cloud_outlined,
|
||||
titel: 'Cloud Sync',
|
||||
@@ -219,14 +229,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
titel: 'Musikserver',
|
||||
untertitel: 'Navidrome Musik-Server verbinden',
|
||||
onTap: () {
|
||||
// Signal zum Öffnen des Server-Browsers
|
||||
Navigator.pop(context, 'server');
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ─── Sektion: Cloud-Sync ───
|
||||
_sektionHeader('Cloud-Sync'),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// ─── Sync & Wiedergabe ───
|
||||
_einstellungsGruppe([
|
||||
_gruppenZeile(
|
||||
label: 'Sync-Modus',
|
||||
@@ -284,7 +293,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_gruppenZeile(
|
||||
label: 'Wiedergabegeschwindigkeit',
|
||||
label: 'Geschwindigkeit',
|
||||
wert: '${_geschwindigkeit.toStringAsFixed(2)}x',
|
||||
inhalt: Slider(
|
||||
value: _geschwindigkeit,
|
||||
@@ -297,10 +306,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ─── Sektion: Aussehen ───
|
||||
_sektionHeader('Aussehen'),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// ─── Aussehen ───
|
||||
_einstellungsGruppe([
|
||||
_gruppenZeile(
|
||||
label: 'Akzentfarbe',
|
||||
@@ -312,15 +321,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
]),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ─── Sektion: Daten & Privatsphäre ───
|
||||
_sektionHeader('Daten & Privatsphäre'),
|
||||
_infoKachel(
|
||||
icon: Icons.storage_outlined,
|
||||
titel: 'Lokale Musik',
|
||||
wert: _speicherText,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// ─── Recap ───
|
||||
_einstellungsKachel(
|
||||
icon: Icons.auto_graph,
|
||||
titel: 'Recap (Woche/Monat/Jahr)',
|
||||
@@ -332,77 +336,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ─── Sektion: Erweitert (Technik) ───
|
||||
_sektionHeader('Erweitert'),
|
||||
_einstellungsKachel(
|
||||
icon: Icons.handyman_outlined,
|
||||
titel: 'Erweitert',
|
||||
untertitel: 'Logs, Diagnose, Entwickler & Scanner',
|
||||
onTap: () async {
|
||||
// Erweitert-Screen öffnen. „Musik scannen“ (onScan) schließt den
|
||||
// Erweitert-Screen mit dem Ergebnis 'scanner' → hier awaiten und
|
||||
// das Ergebnis an MeloHome weiterreichen, das den Scanner startet.
|
||||
final result = await Navigator.push<String>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ErweitertScreen(
|
||||
onScan: () {
|
||||
Navigator.pop(context, 'scanner');
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!mounted || result != 'scanner') return;
|
||||
Navigator.pop(this.context, 'scanner');
|
||||
},
|
||||
),
|
||||
_einstellungsKachel(
|
||||
icon: Icons.article_outlined,
|
||||
titel: 'Logs',
|
||||
untertitel: 'App-Logbuch im Speicher ansehen',
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const LogViewerScreen()),
|
||||
);
|
||||
},
|
||||
),
|
||||
_einstellungsKachel(
|
||||
icon: Icons.bug_report_outlined,
|
||||
titel: 'Entwickler',
|
||||
untertitel: 'Version, URLs & technische Details',
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const EntwicklerScreen()),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// ─── Sektion: Info ───
|
||||
_sektionHeader('Info'),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel1,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: MeloTheme.dunkel2),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_infoZeile('Version', '2.52.3'),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_infoZeile('Theme', 'Schwarz + Akzent'),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_infoZeile('Auth', AppConfig.authUrl),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_infoZeile('Cloud', AppConfig.cloudUrl),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// ─── Abmelden ───
|
||||
SizedBox(
|
||||
@@ -415,29 +350,180 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
icon: const Icon(Icons.logout, size: 18),
|
||||
label: const Text('Abmelden', style: TextStyle(fontSize: 14)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: MeloTheme.rot,
|
||||
side: const BorderSide(color: MeloTheme.rot),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
|
||||
foregroundColor: MeloTheme.akzent,
|
||||
side: BorderSide(color: MeloTheme.akzent),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(999)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// ═══ Expertenmodus ═══
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: MeloTheme.dunkel2),
|
||||
),
|
||||
child: SwitchListTile(
|
||||
key: const Key('experten_modus'),
|
||||
title: const Text(
|
||||
'Expertenmodus',
|
||||
style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: const Text(
|
||||
'Erweiterte Einstellungen & Diagnose',
|
||||
style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 12),
|
||||
),
|
||||
value: _expertenModus,
|
||||
activeTrackColor: MeloTheme.akzent,
|
||||
activeThumbColor: MeloTheme.akzent,
|
||||
onChanged: _expertenModusSetzen,
|
||||
secondary: Icon(Icons.science_outlined, color: MeloTheme.akzent, size: 22),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
),
|
||||
),
|
||||
|
||||
// ═══ Erweiterte Sektion (nur wenn Expertenmodus an) ═══
|
||||
if (_expertenModus) ...[
|
||||
const SizedBox(height: 4),
|
||||
_sektionHeader('🔬 Erweitert', icon: Icons.science),
|
||||
|
||||
// ─── Storage-Info ───
|
||||
_infoKachel(
|
||||
icon: Icons.storage_outlined,
|
||||
titel: 'Lokale Musik',
|
||||
wert: _speicherText,
|
||||
),
|
||||
|
||||
// ─── Sync-Details ───
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: MeloTheme.dunkel2),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_infoZeile('Letzter Sync', _letzterSync ?? 'Nie'),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_infoZeile('Modus', _syncModusText),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_infoZeile('Verbindung', _verbindungsStatus ?? '–'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ─── Server-URL-Anzeige ───
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: MeloTheme.dunkel2),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_infoZeile('Auth-URL', AppConfig.authUrl),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_infoZeile('Cloud-URL', AppConfig.cloudUrl),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_infoZeile('Musikserver', AppConfig.navidromeUrl),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// ─── Logs & DB-Wartung ───
|
||||
_einstellungsKachel(
|
||||
icon: Icons.article_outlined,
|
||||
titel: 'Logs',
|
||||
untertitel: 'App-Logbuch im Speicher ansehen',
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const LogViewerScreen()),
|
||||
);
|
||||
},
|
||||
),
|
||||
_einstellungsKachel(
|
||||
icon: Icons.handyman_outlined,
|
||||
titel: 'DB-Wartung & Diagnose',
|
||||
untertitel: 'Datenbank-Optimierung, Scanner & mehr',
|
||||
onTap: () async {
|
||||
final result = await Navigator.push<String>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ErweitertScreen(
|
||||
onScan: () {
|
||||
Navigator.pop(context, 'scanner');
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!mounted || result != 'scanner') return;
|
||||
Navigator.pop(this.context, 'scanner');
|
||||
},
|
||||
),
|
||||
_einstellungsKachel(
|
||||
icon: Icons.bug_report_outlined,
|
||||
titel: 'Entwickler',
|
||||
untertitel: 'Version, URLs & technische Details',
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const EntwicklerScreen()),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// ─── Info ───
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: MeloTheme.dunkel2),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_infoZeile('Version', '2.56.0'),
|
||||
const Divider(color: MeloTheme.dunkel2, height: 1),
|
||||
_infoZeile('Theme', 'Melo Dark Fusion'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _sektionHeader(String titel) {
|
||||
// ─── ─── Hilfs-Widgets ─── ───
|
||||
|
||||
Widget _sektionHeader(String titel, {IconData? icon}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(4, 16, 4, 8),
|
||||
child: Text(
|
||||
titel,
|
||||
style: const TextStyle(
|
||||
color: MeloTheme.textSekundaer,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (icon != null) ...[
|
||||
Icon(icon, size: 16, color: MeloTheme.akzent),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
Text(
|
||||
titel,
|
||||
style: const TextStyle(
|
||||
color: MeloTheme.textSekundaer,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -452,14 +538,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Material(
|
||||
color: MeloTheme.dunkel1,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: MeloTheme.dunkel2),
|
||||
),
|
||||
child: Row(
|
||||
@@ -469,9 +555,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel2,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Icon(icon, color: MeloTheme.rot, size: 20),
|
||||
child: Icon(icon, color: MeloTheme.akzent, size: 20),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
@@ -499,7 +585,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Informations-Kachel (nicht antippbar) — z.B. Speicher-Info.
|
||||
Widget _infoKachel({
|
||||
required IconData icon,
|
||||
required String titel,
|
||||
@@ -511,7 +596,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel1,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: MeloTheme.dunkel2),
|
||||
),
|
||||
child: Row(
|
||||
@@ -521,9 +606,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel2,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Icon(icon, color: MeloTheme.rot, size: 20),
|
||||
child: Icon(icon, color: MeloTheme.akzent, size: 20),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
@@ -542,13 +627,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Gruppe aus mehreren Einstellungs-Zeilen in einer abgerundeten Box.
|
||||
Widget _einstellungsGruppe(List<Widget> kinder) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: MeloTheme.dunkel1,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: MeloTheme.dunkel2),
|
||||
),
|
||||
child: Column(children: kinder),
|
||||
@@ -595,7 +679,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||
decoration: BoxDecoration(
|
||||
color: aktiv ? MeloTheme.akzent : MeloTheme.dunkel2,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
@@ -622,6 +706,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
color: aktiv ? Colors.white : Colors.transparent,
|
||||
width: 2.5,
|
||||
),
|
||||
boxShadow: aktiv ? [
|
||||
BoxShadow(
|
||||
color: farbe.withValues(alpha: 0.4),
|
||||
blurRadius: 12,
|
||||
),
|
||||
] : null,
|
||||
),
|
||||
child: aktiv ? const Icon(Icons.check, size: 18, color: Colors.white) : null,
|
||||
),
|
||||
@@ -647,7 +737,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Anzeigetext für den aktuellen Sync-Modus.
|
||||
String get _syncModusText {
|
||||
switch (_syncModus) {
|
||||
case 'realtime':
|
||||
@@ -661,19 +750,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Radio-ähnliches Kachel-Widget für Sync-Modus-Auswahl.
|
||||
Widget _modusRadioTile(String titel, String wert, String beschreibung) {
|
||||
final ausgewaehlt = _syncModus == wert;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: InkWell(
|
||||
onTap: () => _syncModusSetzen(wert),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: ausgewaehlt ? MeloTheme.akzent.withAlpha(25) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: ausgewaehlt ? MeloTheme.akzent : MeloTheme.dunkel2,
|
||||
width: ausgewaehlt ? 1.5 : 1,
|
||||
|
||||
Reference in New Issue
Block a user