feat(ui): design token discipline, accessibility and i18n pass

- Replace all hardcoded Color literals outside lib/tema with theme tokens (new static brand palette in PluriWaveTokens); media notification uses the brand color instead of the Material default purple
- Favorite button on station cards grows to a 48dp target and becomes an independent semantics node for screen readers (Semantics container fix)
- All flutter_animate call sites route through the PluriAnimate reduced-motion gate (zero direct .animate() left)
- Locale-aware short dates via intl DateFormat (new lib/l10n/formato_fechas.dart) replacing the hardcoded DD/MM/YYYY; proper plural messages for the favorites counter; example stream URL as a localized key - all 13 locales
- Rounded shimmer placeholders matching card radii; shimmer loading state in search instead of a bare spinner; rounded icon variants unified in settings; bottom-sheet conventions on the custom station form
- Fix latent debug crash: vacation editor read AppLocalizations in initState
- 11 new tests (121 total green), flutter analyze clean
This commit is contained in:
2026-06-11 23:42:16 +02:00
parent 52855e75c2
commit 202bef3539
49 changed files with 1108 additions and 175 deletions
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "الخميس",
"weekdayShortFriday": "الجمعة",
"weekdayShortSaturday": "السبت",
"weekdayShortSunday": "الأحد"
"weekdayShortSunday": "الأحد",
"stationCount": "{count, plural, one{محطة واحدة} two{محطتان} few{{count} محطات} other{{count} محطة}}",
"alarmIconLabel": "منبه موسيقي",
"vacationIconLabel": "وضع الإجازة",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "বৃহস্পতি",
"weekdayShortFriday": "শুক্র",
"weekdayShortSaturday": "শনি",
"weekdayShortSunday": "রবি"
"weekdayShortSunday": "রবি",
"stationCount": "{count, plural, =1{১টি স্টেশন} other{{count}টি স্টেশন}}",
"alarmIconLabel": "মিউজিক অ্যালার্ম",
"vacationIconLabel": "ছুটির মোড",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "Do",
"weekdayShortFriday": "Fr",
"weekdayShortSaturday": "Sa",
"weekdayShortSunday": "So"
"weekdayShortSunday": "So",
"stationCount": "{count, plural, =1{1 Sender} other{{count} Sender}}",
"alarmIconLabel": "Musikwecker",
"vacationIconLabel": "Urlaubsmodus",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "Thu",
"weekdayShortFriday": "Fri",
"weekdayShortSaturday": "Sat",
"weekdayShortSunday": "Sun"
"weekdayShortSunday": "Sun",
"stationCount": "{count, plural, =1{1 station} other{{count} stations}}",
"alarmIconLabel": "Musical alarm",
"vacationIconLabel": "Vacation mode",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+12 -2
View File
@@ -568,6 +568,16 @@
"weekdayShortThursday": "Jue",
"weekdayShortFriday": "Vie",
"weekdayShortSaturday": "Sáb",
"weekdayShortSunday": "Dom"
"weekdayShortSunday": "Dom",
"stationCount": "{count, plural, =1{1 emisora} other{{count} emisoras}}",
"alarmIconLabel": "Alarma musical",
"vacationIconLabel": "Modo vacaciones",
"streamUrlHint": "https://stream.example.com:8000/radio",
"@stationCount": {
"placeholders": {
"count": {
"type": "int"
}
}
}
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "Jeu",
"weekdayShortFriday": "Ven",
"weekdayShortSaturday": "Sam",
"weekdayShortSunday": "Dim"
"weekdayShortSunday": "Dim",
"stationCount": "{count, plural, =1{1 station} other{{count} stations}}",
"alarmIconLabel": "Alarme musicale",
"vacationIconLabel": "Mode vacances",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "गुरु",
"weekdayShortFriday": "शुक्र",
"weekdayShortSaturday": "शनि",
"weekdayShortSunday": "रवि"
"weekdayShortSunday": "रवि",
"stationCount": "{count, plural, =1{1 स्टेशन} other{{count} स्टेशन}}",
"alarmIconLabel": "संगीत अलार्म",
"vacationIconLabel": "अवकाश मोड",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "Kam",
"weekdayShortFriday": "Jum",
"weekdayShortSaturday": "Sab",
"weekdayShortSunday": "Min"
"weekdayShortSunday": "Min",
"stationCount": "{count, plural, other{{count} stasiun}}",
"alarmIconLabel": "Alarm musik",
"vacationIconLabel": "Mode liburan",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "Gio",
"weekdayShortFriday": "Ven",
"weekdayShortSaturday": "Sab",
"weekdayShortSunday": "Dom"
"weekdayShortSunday": "Dom",
"stationCount": "{count, plural, =1{1 stazione} other{{count} stazioni}}",
"alarmIconLabel": "Sveglia musicale",
"vacationIconLabel": "Modalità vacanza",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "木",
"weekdayShortFriday": "金",
"weekdayShortSaturday": "土",
"weekdayShortSunday": "日"
"weekdayShortSunday": "日",
"stationCount": "{count, plural, other{{count}局}}",
"alarmIconLabel": "ミュージックアラーム",
"vacationIconLabel": "休暇モード",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "Qui",
"weekdayShortFriday": "Sex",
"weekdayShortSaturday": "Sáb",
"weekdayShortSunday": "Dom"
"weekdayShortSunday": "Dom",
"stationCount": "{count, plural, =1{1 estação} other{{count} estações}}",
"alarmIconLabel": "Alarme musical",
"vacationIconLabel": "Modo férias",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "Чт",
"weekdayShortFriday": "Пт",
"weekdayShortSaturday": "Сб",
"weekdayShortSunday": "Вс"
"weekdayShortSunday": "Вс",
"stationCount": "{count, plural, one{{count} станция} few{{count} станции} other{{count} станций}}",
"alarmIconLabel": "Музыкальный будильник",
"vacationIconLabel": "Режим отпуска",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+5 -1
View File
@@ -612,5 +612,9 @@
"weekdayShortThursday": "周四",
"weekdayShortFriday": "周五",
"weekdayShortSaturday": "周六",
"weekdayShortSunday": "周日"
"weekdayShortSunday": "周日",
"stationCount": "{count, plural, other{{count} 个电台}}",
"alarmIconLabel": "音乐闹钟",
"vacationIconLabel": "假期模式",
"streamUrlHint": "https://stream.example.com:8000/radio"
}
+13
View File
@@ -0,0 +1,13 @@
import 'package:intl/intl.dart';
/// Locale-aware short date (S5-R4).
///
/// Replaces the old hardcoded `DD/MM/YYYY` pattern, which was wrong for
/// locales like en-US (M/D/Y) or ja (Y/M/D). [localeTag] accepts both
/// BCP-47 ('en-US') and ICU ('en_US') forms — intl canonicalizes them.
///
/// Date symbols for the active locale are loaded by
/// GlobalMaterialLocalizations, so any widget below MaterialApp can call
/// this safely.
String fechaCortaLocalizada(String localeTag, DateTime fecha) =>
DateFormat.yMd(localeTag).format(fecha);
+24
View File
@@ -2245,6 +2245,30 @@ abstract class AppLocalizations {
/// In es, this message translates to:
/// **'Dom'**
String get weekdayShortSunday;
/// No description provided for @stationCount.
///
/// In es, this message translates to:
/// **'{count, plural, =1{1 emisora} other{{count} emisoras}}'**
String stationCount(int count);
/// No description provided for @alarmIconLabel.
///
/// In es, this message translates to:
/// **'Alarma musical'**
String get alarmIconLabel;
/// No description provided for @vacationIconLabel.
///
/// In es, this message translates to:
/// **'Modo vacaciones'**
String get vacationIconLabel;
/// No description provided for @streamUrlHint.
///
/// In es, this message translates to:
/// **'https://stream.example.com:8000/radio'**
String get streamUrlHint;
}
class _AppLocalizationsDelegate
+22
View File
@@ -1203,4 +1203,26 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get weekdayShortSunday => 'الأحد';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count محطة',
few: '$count محطات',
two: 'محطتان',
one: 'محطة واحدة',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'منبه موسيقي';
@override
String get vacationIconLabel => 'وضع الإجازة';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+20
View File
@@ -1212,4 +1212,24 @@ class AppLocalizationsBn extends AppLocalizations {
@override
String get weekdayShortSunday => 'রবি';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$countটি স্টেশন',
one: '১টি স্টেশন',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'মিউজিক অ্যালার্ম';
@override
String get vacationIconLabel => 'ছুটির মোড';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+20
View File
@@ -1222,4 +1222,24 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get weekdayShortSunday => 'So';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count Sender',
one: '1 Sender',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'Musikwecker';
@override
String get vacationIconLabel => 'Urlaubsmodus';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+20
View File
@@ -1208,4 +1208,24 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get weekdayShortSunday => 'Sun';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count stations',
one: '1 station',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'Musical alarm';
@override
String get vacationIconLabel => 'Vacation mode';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+20
View File
@@ -1217,4 +1217,24 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get weekdayShortSunday => 'Dom';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count emisoras',
one: '1 emisora',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'Alarma musical';
@override
String get vacationIconLabel => 'Modo vacaciones';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+20
View File
@@ -1227,4 +1227,24 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get weekdayShortSunday => 'Dim';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count stations',
one: '1 station',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'Alarme musicale';
@override
String get vacationIconLabel => 'Mode vacances';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+20
View File
@@ -1211,4 +1211,24 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get weekdayShortSunday => 'रवि';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count स्टेशन',
one: '1 स्टेशन',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'संगीत अलार्म';
@override
String get vacationIconLabel => 'अवकाश मोड';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+19
View File
@@ -1217,4 +1217,23 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get weekdayShortSunday => 'Min';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count stasiun',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'Alarm musik';
@override
String get vacationIconLabel => 'Mode liburan';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+20
View File
@@ -1222,4 +1222,24 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get weekdayShortSunday => 'Dom';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count stazioni',
one: '1 stazione',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'Sveglia musicale';
@override
String get vacationIconLabel => 'Modalità vacanza';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+19
View File
@@ -1173,4 +1173,23 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get weekdayShortSunday => '';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count局',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'ミュージックアラーム';
@override
String get vacationIconLabel => '休暇モード';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+20
View File
@@ -1214,4 +1214,24 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get weekdayShortSunday => 'Dom';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count estações',
one: '1 estação',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'Alarme musical';
@override
String get vacationIconLabel => 'Modo férias';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+21
View File
@@ -1217,4 +1217,25 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get weekdayShortSunday => 'Вс';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count станций',
few: '$count станции',
one: '$count станция',
);
return '$_temp0';
}
@override
String get alarmIconLabel => 'Музыкальный будильник';
@override
String get vacationIconLabel => 'Режим отпуска';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}
+19
View File
@@ -1168,4 +1168,23 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get weekdayShortSunday => '周日';
@override
String stationCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count 个电台',
);
return '$_temp0';
}
@override
String get alarmIconLabel => '音乐闹钟';
@override
String get vacationIconLabel => '假期模式';
@override
String get streamUrlHint => 'https://stream.example.com:8000/radio';
}