feat: UI-Umbau - weiches Dunkel, Schriftgroessen-Regler, Sprechblasen umschaltbar, Einstellungen als Vollbild

Bernds Entscheidungen 2026-10-07:
- theme.dart: ThemeVariant (Dunkel = neues weiches Dunkelgrau als Standard,
  Schwarz = bisheriges Fast-Schwarz, Hell) mit PyramidPalette; Nebentexte
  kontrastreicher. Alte Einstellung theme_dark wird uebernommen
  (dunkel -> weiches Dunkel). Fest verdrahtete Dunkelfarben im Profil-Popup
  und GIF-Picker folgen jetzt dem Farbmodus.
- appearance.dart: Schriftgroesse = Pyramid-Regler x Systemeinstellung
  (Handy-Standard 115 %, PC 100 %), MessageStyle Automatisch/Sprechblasen/
  Liste, gemeinsame isMobileLayout-Regel.
- message_bubbles.dart: Sprechblasen-Darstellung (eigene rechts in
  Akzentfarbe, Gruppen mit Name + Avatar, Bilder ohne Blase in voller Groesse);
  Listen-Darstellung unveraendert, Weiche in MessageGroup.
- Formatierte Nachrichten (u. a. Antworten) nutzten RichText ohne
  Schriftskalierung -> erschienen kleiner; jetzt Text.rich.
- Einstellungen auf dem Handy als ganze Seite, Schalter unter dem Text.
- UI-Durchlauf um Liste/Hell/Gruppe/Einstellungen erweitert; Tests fuer
  Theme-Varianten, Uebernahme alter Werte, Schriftfaktor, Darstellung.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Bernd Steckmeister
2026-10-07 17:16:07 +02:00
co-authored by Claude Opus 5.5
parent a1d64ec813
commit c5f10901e6
17 changed files with 973 additions and 102 deletions
+102 -4
View File
@@ -1,5 +1,8 @@
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pyramid/core/appearance.dart';
import 'package:pyramid/core/settings_prefs.dart';
import 'package:pyramid/core/theme.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Tests für die Storage-Fassade, die laut M2-Modulschnitt zur Pflicht-
@@ -94,22 +97,117 @@ void main() {
group('Theme-Prefs', () {
test('leerer Store liefert die dokumentierten Defaults', () async {
final t = await loadThemePrefs();
expect(t.isDark, true);
expect(
t.variant,
ThemeVariant.soft,
reason: 'Standard seit 2026-10-07: weiches Dunkel',
);
expect(t.accentIdx, 0);
expect(t.radius, 12.0);
expect(t.motion, 1.0);
expect(t.textScale, isNull, reason: 'null = automatisch');
expect(t.messageStyle, MessageStyle.auto);
});
test('Roundtrip: gespeicherte Werte kommen zurück, Teil-Save lässt '
'den Rest unangetastet', () async {
await saveThemePrefs(isDark: false, accentIdx: 3, radius: 8, motion: 0.5);
await saveThemePrefs(
variant: ThemeVariant.light,
accentIdx: 3,
radius: 8,
motion: 0.5,
textScale: 1.3,
messageStyle: MessageStyle.list,
);
var t = await loadThemePrefs();
expect((t.isDark, t.accentIdx, t.radius, t.motion), (false, 3, 8.0, 0.5));
expect(
(
t.variant,
t.accentIdx,
t.radius,
t.motion,
t.textScale,
t.messageStyle,
),
(ThemeVariant.light, 3, 8.0, 0.5, 1.3, MessageStyle.list),
);
// Nur ein Feld ändern – die anderen dürfen nicht zurückfallen.
await saveThemePrefs(accentIdx: 1);
t = await loadThemePrefs();
expect((t.isDark, t.accentIdx, t.radius, t.motion), (false, 1, 8.0, 0.5));
expect(
(
t.variant,
t.accentIdx,
t.radius,
t.motion,
t.textScale,
t.messageStyle,
),
(ThemeVariant.light, 1, 8.0, 0.5, 1.3, MessageStyle.list),
);
// Schriftgröße zurück auf automatisch.
await saveThemePrefs(resetTextScale: true);
t = await loadThemePrefs();
expect(t.textScale, isNull);
});
test(
'Übernahme alter Einstellungen (vor 2026-10-07 nur theme_dark)',
() async {
SharedPreferences.setMockInitialValues({'theme_dark': true});
expect(
(await loadThemePrefs()).variant,
ThemeVariant.soft,
reason: 'wer dunkel hatte, bekommt das neue weiche Dunkel',
);
SharedPreferences.setMockInitialValues({'theme_dark': false});
expect((await loadThemePrefs()).variant, ThemeVariant.light);
},
);
test(
'Variante „Schwarz“ wird gespeichert und theme_dark mitgepflegt',
() async {
await saveThemePrefs(variant: ThemeVariant.black);
final p = await SharedPreferences.getInstance();
expect(p.getString('theme_variant'), 'black');
expect(
p.getBool('theme_dark'),
true,
reason: 'ältere App-Versionen lesen nur theme_dark',
);
expect((await loadThemePrefs()).variant, ThemeVariant.black);
},
);
});
group('Darstellung', () {
test('Sprechblasen: Automatisch = Handy ja, PC nein', () {
expect(MessageStyle.auto.useBubbles(mobile: true), isTrue);
expect(MessageStyle.auto.useBubbles(mobile: false), isFalse);
expect(MessageStyle.bubbles.useBubbles(mobile: false), isTrue);
expect(MessageStyle.list.useBubbles(mobile: true), isFalse);
});
test('Schriftfaktor wirkt zusätzlich zur Systemeinstellung', () {
const system = TextScaler.linear(1.2);
const scaler = PyramidTextScaler(system, 1.15);
expect(scaler.scale(14), closeTo(14 * 1.15 * 1.2, 0.001));
expect(defaultTextScale(mobile: true), greaterThan(1.0));
expect(defaultTextScale(mobile: false), 1.0);
});
test('Handy-Layout-Grenze', () {
expect(isMobileLayout(const Size(411, 891)), isTrue);
expect(isMobileLayout(const Size(1280, 720)), isFalse);
expect(
isMobileLayout(const Size(891, 411)),
isTrue,
reason: 'Handy quer (flach) bleibt Handy-Layout',
);
});
});