Commit cbdefead authored by Yegor's avatar Yegor Committed by GitHub

Localize time picker (#11967)

* Internationalize the time picker

- header layout and formatting
- 12-hour vs 24-hour dial
- RTL

* make TimeOfDayFormat an enum

* address comments
parent 72cc92cb
...@@ -76,7 +76,7 @@ String generateLocalizationsMap() { ...@@ -76,7 +76,7 @@ String generateLocalizationsMap() {
const Map<String, Map<String, String>> localizations = const <String, Map<String, String>> {'''); const Map<String, Map<String, String>> localizations = const <String, Map<String, String>> {''');
final String lastLocale = localeToResources.keys.last; final String lastLocale = localeToResources.keys.last;
for (String locale in localeToResources.keys) { for (String locale in localeToResources.keys.toList()..sort()) {
output.writeln(' "$locale": const <String, String>{'); output.writeln(' "$locale": const <String, String>{');
final Map<String, String> resources = localeToResources[locale]; final Map<String, String> resources = localeToResources[locale];
...@@ -126,7 +126,7 @@ void main(List<String> args) { ...@@ -126,7 +126,7 @@ void main(List<String> args) {
} }
} }
final String regenerate = 'dart gen_localizations ${directory.path} ${args[1]}'; final String regenerate = 'dart dev/tools/gen_localizations.dart ${directory.path} ${args[1]}';
print(outputHeader.replaceFirst('@(regenerate)', regenerate)); print(outputHeader.replaceFirst('@(regenerate)', regenerate));
print(generateLocalizationsMap()); print(generateLocalizationsMap());
} }
...@@ -4,134 +4,15 @@ ...@@ -4,134 +4,15 @@
// This file has been automatically generated. Please do not edit it manually. // This file has been automatically generated. Please do not edit it manually.
// To regenerate the file, use: // To regenerate the file, use:
// dart gen_localizations packages/flutter/lib/src/material/i18n material // dart dev/tools/gen_localizations.dart lib/src/material/i18n material
/// Maps from [Locale.languageCode] to a map that contains the localized strings /// Maps from [Locale.languageCode] to a map that contains the localized strings
/// for that locale. /// for that locale.
/// ///
/// This variable is used by [MaterialLocalizations]. /// This variable is used by [MaterialLocalizations].
const Map<String, Map<String, String>> localizations = const <String, Map<String, String>> { const Map<String, Map<String, String>> localizations = const <String, Map<String, String>> {
"sd": const <String, String>{
"openAppDrawerTooltip": r"اوپن جي مينڊيٽ مينيو",
"backButtonTooltip": r"پوئتي",
"closeButtonTooltip": r"بند ڪريو",
"nextMonthTooltip": r"ايندڙ مهيني",
"previousMonthTooltip": r"پويون مهينو",
"nextPageTooltip": r"اڳيون پيج",
"previousPageTooltip": r"پويون صفحو",
"showMenuTooltip": r"ڏيکاريو",
"licensesPageTitle": r"لائسنس",
"pageRowsInfoTitle": r"$firstRow–$lastRow جي $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow کان $rowCount تقريبن",
"rowsPerPageTitle": r"رني پاسي وارو صفحو",
"selectedRowCountTitleOther": r"$selectedRowCount شيون چونڊيل",
"cancelButtonLabel": r"منسوخ ڪيو",
"closeButtonLabel": r"بند ڪريو",
"continueButtonLabel": r"جاري رکو",
"copyButtonLabel": r"ڪاپي",
"cutButtonLabel": r"پٽي",
"okButtonLabel": r"ٺيڪ آهي",
"pasteButtonLabel": r"پيسٽ ڪريو",
"selectAllButtonLabel": r"سڀ چونڊيو",
"viewLicensesButtonLabel": r"لائسنس ڏسو"
},
"fa": const <String, String>{
"openAppDrawerTooltip": r"منوی ناوبری را باز کنید",
"backButtonTooltip": r"بازگشت",
"closeButtonTooltip": r"بستن",
"nextMonthTooltip": r"ماه بعد",
"previousMonthTooltip": r"ماه گذشته",
"nextPageTooltip": r"صفحه بعد",
"previousPageTooltip": r"صفحه قبلی",
"showMenuTooltip": r"نمایش منو",
"licensesPageTitle": r"مجوز",
"pageRowsInfoTitle": r"$firstRow–$lastRow از $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow از حدود $rowCount",
"rowsPerPageTitle": r"ردیف در صفحه:",
"selectedRowCountTitleOther": r"$selectedRowCount آیتم های انتخاب شده",
"cancelButtonLabel": r"لغو",
"closeButtonLabel": r"بستن",
"continueButtonLabel": r"ادامه دهید",
"copyButtonLabel": r"کپی",
"cutButtonLabel": r"برش",
"okButtonLabel": r"تایید",
"pasteButtonLabel": r"چسباندن",
"selectAllButtonLabel": r"انتخاب همه",
"viewLicensesButtonLabel": r"مشاهده مجوز"
},
"ur": const <String, String>{
"openAppDrawerTooltip": r"کھولیں نیویگیشن مینو",
"backButtonTooltip": r"واپس",
"closeButtonTooltip": r"بند کریں",
"nextMonthTooltip": r"اگلا مھینہ",
"previousMonthTooltip": r"پچھلا مھینہ",
"nextPageTooltip": r"اگلا صفحہ",
"previousPageTooltip": r"سابقہ ​​صفحہ",
"showMenuTooltip": r"مینو دکھائیں",
"licensesPageTitle": r"لائسنس",
"pageRowsInfoTitle": r"$firstRow–$lastRow کے $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow میں سے $rowCount تقریبا",
"rowsPerPageTitle": r"رویوں فی صفحہ:",
"selectedRowCountTitleOther": r"$selectedRowCount منتخب کردہ اشیاء",
"cancelButtonLabel": r"منسوخ کریں",
"closeButtonLabel": r"بند کریں",
"continueButtonLabel": r"جاری رکھیں",
"copyButtonLabel": r"کاپی",
"cutButtonLabel": r"کاٹیں",
"okButtonLabel": r"ٹھیک ہے",
"pasteButtonLabel": r"چسپاں",
"selectAllButtonLabel": r"تکاپیمام منتخب کریں",
"viewLicensesButtonLabel": r"لائسنس دیکھیں"
},
"ps": const <String, String>{
"openAppDrawerTooltip": r"د پرانیستی نیینګ مینو",
"backButtonTooltip": r"شاته",
"closeButtonTooltip": r"بنده",
"nextMonthTooltip": r"بله میاشت",
"previousMonthTooltip": r"تیره میاشت",
"nextPageTooltip": r"بله پاڼه",
"previousPageTooltip": r"مخکینی مخ",
"showMenuTooltip": r"غورنۍ ښودل",
"licensesPageTitle": r"جوازونه",
"pageRowsInfoTitle": r"$firstRow–$lastRow د $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow څخه $rowCount د",
"rowsPerPageTitle": r"د هرې پاڼې پاڼې:",
"selectedRowCountTitleOther": r"$selectedRowCount توکي غوره شوي",
"cancelButtonLabel": r"لغوه کول",
"closeButtonLabel": r"تړل",
"continueButtonLabel": r"منځپانګې",
"copyButtonLabel": r"کاپی",
"cutButtonLabel": r"کم کړئ",
"okButtonLabel": r"سمه ده",
"pasteButtonLabel": r"پیټ کړئ",
"selectAllButtonLabel": r"غوره کړئ",
"viewLicensesButtonLabel": r"لیدلس وګورئ"
},
"he": const <String, String>{
"openAppDrawerTooltip": r"פתח תפריט ניווט",
"backButtonTooltip": r"אחורה",
"closeButtonTooltip": r"סגור",
"nextMonthTooltip": r"חודש הבא",
"previousMonthTooltip": r"חודש שעבר",
"nextPageTooltip": r"עמוד הבא",
"previousPageTooltip": r"עמוד קודם",
"showMenuTooltip": r"הצג תפריט",
"licensesPageTitle": r"רישיונות",
"pageRowsInfoTitle": r"$firstRow–$lastRow מתוך $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow מתוך כ $rowCount",
"rowsPerPageTitle": r"שורות לעמוד:",
"selectedRowCountTitleOther": r"$selectedRowCount פריטים שנבחרו",
"cancelButtonLabel": r"ביטול",
"closeButtonLabel": r"סגור",
"continueButtonLabel": r"לְהַמשִׁיך",
"copyButtonLabel": r"קיבלתי!",
"cutButtonLabel": r"גזור",
"okButtonLabel": r"בסדר",
"pasteButtonLabel": r"הדבק",
"selectAllButtonLabel": r"בחר הכל",
"viewLicensesButtonLabel": r"ראה רישיונות"
},
"ar": const <String, String>{ "ar": const <String, String>{
"timeOfDayFormat": r"h:mm a",
"openAppDrawerTooltip": r"افتح قائمة التنقل", "openAppDrawerTooltip": r"افتح قائمة التنقل",
"backButtonTooltip": r"الى الخلف", "backButtonTooltip": r"الى الخلف",
"closeButtonTooltip": r"إغلا", "closeButtonTooltip": r"إغلا",
...@@ -153,57 +34,77 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String ...@@ -153,57 +34,77 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"okButtonLabel": r"حسنا", "okButtonLabel": r"حسنا",
"pasteButtonLabel": r"عجين", "pasteButtonLabel": r"عجين",
"selectAllButtonLabel": r"اختر الكل", "selectAllButtonLabel": r"اختر الكل",
"viewLicensesButtonLabel": r"عرض التراخيص" "viewLicensesButtonLabel": r"عرض التراخيص",
"anteMeridiemAbbreviation": r"ص",
"postMeridiemAbbreviation": r"م"
}, },
"it": const <String, String>{ "de": const <String, String>{
"openAppDrawerTooltip": r"Apri il menu di navigazione", "timeOfDayFormat": r"HH:mm",
"backButtonTooltip": r"Indietro", "openAppDrawerTooltip": r"Navigationsmenü öffnen",
"closeButtonTooltip": r"Chiudi", "backButtonTooltip": r"Zurück",
"nextMonthTooltip": r"Il prossimo mese", "closeButtonTooltip": r"Schließen",
"previousMonthTooltip": r"Il mese scorso", "nextMonthTooltip": r"Nächster Monat",
"nextPageTooltip": r"Pagina successiva", "previousMonthTooltip": r"Vorheriger Monat",
"previousPageTooltip": r"Pagina precedente", "nextPageTooltip": r"Nächste Seite",
"showMenuTooltip": r"Mostra il menu", "previousPageTooltip": r"Vorherige Seite",
"licensesPageTitle": r"Licenze", "showMenuTooltip": r"Menü anzeigen",
"pageRowsInfoTitle": r"$firstRow–$lastRow di $rowCount", "licensesPageTitle": r"Lizenzen",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow di circa $rowCount", "pageRowsInfoTitle": r"$firstRow–$lastRow von $rowCount",
"rowsPerPageTitle": r"Righe per pagina:", "pageRowsInfoTitleApproximate": r"$firstRow–$lastRow von etwa $rowCount",
"selectedRowCountTitleOther": r"$selectedRowCount selezionati", "rowsPerPageTitle": r"Zeilen pro Seite:",
"cancelButtonLabel": r"ANNULLA", "selectedRowCountTitleZero": r"Keine Objekte ausgewählt",
"closeButtonLabel": r"CHIUDI", "selectedRowCountTitleOne": r"1 Objekt ausgewählt",
"continueButtonLabel": r"CONTINUA", "selectedRowCountTitleOther": r"$selectedRowCount Objekte ausgewählt",
"copyButtonLabel": r"COPY", "cancelButtonLabel": r"ABBRECHEN",
"cutButtonLabel": r"TAGLIARE", "closeButtonLabel": r"SCHLIESSEN",
"continueButtonLabel": r"FORTSETZEN",
"copyButtonLabel": r"KOPIEREN",
"cutButtonLabel": r"AUSSCHNEIDEN",
"okButtonLabel": r"OK", "okButtonLabel": r"OK",
"pasteButtonLabel": r"INCOLLA", "pasteButtonLabel": r"EINFÜGEN",
"selectAllButtonLabel": r"SELEZIONA TUTTO", "selectAllButtonLabel": r"ALLES AUSWÄHLEN",
"viewLicensesButtonLabel": r"VEDI LE LICENZE" "viewLicensesButtonLabel": r"LIZENZEN ANZEIGEN"
}, },
"pt": const <String, String>{ "en": const <String, String>{
"openAppDrawerTooltip": r"Abrir menu de navegação", "timeOfDayFormat": r"h:mm a",
"backButtonTooltip": r"Costas", "openAppDrawerTooltip": r"Open navigation menu",
"closeButtonTooltip": r"Fechar", "backButtonTooltip": r"Back",
"nextMonthTooltip": r"Próximo mês", "closeButtonTooltip": r"Close",
"previousMonthTooltip": r"Mês anterior", "nextMonthTooltip": r"Next month",
"nextPageTooltip": r"Próxima página", "previousMonthTooltip": r"Previous month",
"previousPageTooltip": r"Página anterior", "nextPageTooltip": r"Next page",
"showMenuTooltip": r"Mostrar menu", "previousPageTooltip": r"Previous page",
"licensesPageTitle": r"Licenças", "showMenuTooltip": r"Show menu",
"pageRowsInfoTitle": r"$firstRow–$lastRow de $rowCount", "licensesPageTitle": r"Licenses",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow de cerca de $rowCount", "pageRowsInfoTitle": r"$firstRow–$lastRow of $rowCount",
"rowsPerPageTitle": r"Linhas por página:", "pageRowsInfoTitleApproximate": r"$firstRow–$lastRow of about $rowCount",
"selectedRowCountTitleOther": r"$selectedRowCount selecionados", "rowsPerPageTitle": r"Rows per page:",
"cancelButtonLabel": r"CANCELAR", "selectedRowCountTitleZero": r"No items selected",
"closeButtonLabel": r"FECHAR", "selectedRowCountTitleOne": r"1 item selected",
"continueButtonLabel": r"CONTINUAR", "selectedRowCountTitleOther": r"$selectedRowCount items selected",
"copyButtonLabel": r"CÓPIA DE", "cancelButtonLabel": r"CANCEL",
"cutButtonLabel": r"CORTA", "closeButtonLabel": r"CLOSE",
"continueButtonLabel": r"CONTINUE",
"copyButtonLabel": r"COPY",
"cutButtonLabel": r"CUT",
"okButtonLabel": r"OK", "okButtonLabel": r"OK",
"pasteButtonLabel": r"COLAR", "pasteButtonLabel": r"PASTE",
"selectAllButtonLabel": r"SELECIONAR TUDO", "selectAllButtonLabel": r"SELECT ALL",
"viewLicensesButtonLabel": r"VER LICENÇAS" "viewLicensesButtonLabel": r"VIEW LICENSES",
"anteMeridiemAbbreviation": r"AM",
"postMeridiemAbbreviation": r"PM"
},
"en_GB": const <String, String>{
"timeOfDayFormat": r"HH:mm"
},
"en_IE": const <String, String>{
"timeOfDayFormat": r"HH:mm"
},
"en_ZA": const <String, String>{
"timeOfDayFormat": r"HH:mm"
}, },
"es": const <String, String>{ "es": const <String, String>{
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"Abrir el menú de navegación", "openAppDrawerTooltip": r"Abrir el menú de navegación",
"backButtonTooltip": r"Espalda", "backButtonTooltip": r"Espalda",
"closeButtonTooltip": r"Cerrar", "closeButtonTooltip": r"Cerrar",
...@@ -229,7 +130,36 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String ...@@ -229,7 +130,36 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"selectAllButtonLabel": r"SELECCIONAR TODO", "selectAllButtonLabel": r"SELECCIONAR TODO",
"viewLicensesButtonLabel": r"VER LICENCIAS" "viewLicensesButtonLabel": r"VER LICENCIAS"
}, },
"es_US": const <String, String>{
"timeOfDayFormat": r"h:mm a"
},
"fa": const <String, String>{
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"منوی ناوبری را باز کنید",
"backButtonTooltip": r"بازگشت",
"closeButtonTooltip": r"بستن",
"nextMonthTooltip": r"ماه بعد",
"previousMonthTooltip": r"ماه گذشته",
"nextPageTooltip": r"صفحه بعد",
"previousPageTooltip": r"صفحه قبلی",
"showMenuTooltip": r"نمایش منو",
"licensesPageTitle": r"مجوز",
"pageRowsInfoTitle": r"$firstRow–$lastRow از $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow از حدود $rowCount",
"rowsPerPageTitle": r"ردیف در صفحه:",
"selectedRowCountTitleOther": r"$selectedRowCount آیتم های انتخاب شده",
"cancelButtonLabel": r"لغو",
"closeButtonLabel": r"بستن",
"continueButtonLabel": r"ادامه دهید",
"copyButtonLabel": r"کپی",
"cutButtonLabel": r"برش",
"okButtonLabel": r"تایید",
"pasteButtonLabel": r"چسباندن",
"selectAllButtonLabel": r"انتخاب همه",
"viewLicensesButtonLabel": r"مشاهده مجوز"
},
"fr": const <String, String>{ "fr": const <String, String>{
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"Ouvrir le menu de navigation", "openAppDrawerTooltip": r"Ouvrir le menu de navigation",
"backButtonTooltip": r"Retour", "backButtonTooltip": r"Retour",
"closeButtonTooltip": r"Fermer", "closeButtonTooltip": r"Fermer",
...@@ -255,83 +185,61 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String ...@@ -255,83 +185,61 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"selectAllButtonLabel": r"TOUT SÉLECTIONNER", "selectAllButtonLabel": r"TOUT SÉLECTIONNER",
"viewLicensesButtonLabel": r"AFFICHER LES LICENCES" "viewLicensesButtonLabel": r"AFFICHER LES LICENCES"
}, },
"zh": const <String, String>{ "fr_CA": const <String, String>{
"openAppDrawerTooltip": r"打开导航菜单", "timeOfDayFormat": r"HH 'h' mm"
"backButtonTooltip": r"返回",
"closeButtonTooltip": r"关闭",
"nextMonthTooltip": r"下一个月",
"previousMonthTooltip": r"上一个月",
"nextPageTooltip": r"下一页",
"previousPageTooltip": r"上一页",
"showMenuTooltip": r"显示菜单",
"licensesPageTitle": r"许可证",
"pageRowsInfoTitle": r"$rowCount中的$firstRow-$lastRow",
"pageRowsInfoTitleApproximate": r"约$rowCount中的$firstRow-$lastRow",
"rowsPerPageTitle": r"每页行数:",
"selectedRowCountTitleOther": r"$selectedRowCount行所选",
"cancelButtonLabel": r"取消",
"continueButtonLabel": r"继续",
"closeButtonLabel": r"关闭",
"copyButtonLabel": r"复制",
"cutButtonLabel": r"剪切",
"okButtonLabel": r"确定",
"pasteButtonLabel": r"粘贴",
"selectAllButtonLabel": r"全选",
"viewLicensesButtonLabel": r"查看许可证"
}, },
"en": const <String, String>{ "he": const <String, String>{
"openAppDrawerTooltip": r"Open navigation menu", "timeOfDayFormat": r"H:mm",
"backButtonTooltip": r"Back", "openAppDrawerTooltip": r"פתח תפריט ניווט",
"closeButtonTooltip": r"Close", "backButtonTooltip": r"אחורה",
"nextMonthTooltip": r"Next month", "closeButtonTooltip": r"סגור",
"previousMonthTooltip": r"Previous month", "nextMonthTooltip": r"חודש הבא",
"nextPageTooltip": r"Next page", "previousMonthTooltip": r"חודש שעבר",
"previousPageTooltip": r"Previous page", "nextPageTooltip": r"עמוד הבא",
"showMenuTooltip": r"Show menu", "previousPageTooltip": r"עמוד קודם",
"licensesPageTitle": r"Licenses", "showMenuTooltip": r"הצג תפריט",
"pageRowsInfoTitle": r"$firstRow–$lastRow of $rowCount", "licensesPageTitle": r"רישיונות",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow of about $rowCount", "pageRowsInfoTitle": r"$firstRow–$lastRow מתוך $rowCount",
"rowsPerPageTitle": r"Rows per page:", "pageRowsInfoTitleApproximate": r"$firstRow–$lastRow מתוך כ $rowCount",
"selectedRowCountTitleZero": r"No items selected", "rowsPerPageTitle": r"שורות לעמוד:",
"selectedRowCountTitleOne": r"1 item selected", "selectedRowCountTitleOther": r"$selectedRowCount פריטים שנבחרו",
"selectedRowCountTitleOther": r"$selectedRowCount items selected", "cancelButtonLabel": r"ביטול",
"cancelButtonLabel": r"CANCEL", "closeButtonLabel": r"סגור",
"closeButtonLabel": r"CLOSE", "continueButtonLabel": r"לְהַמשִׁיך",
"continueButtonLabel": r"CONTINUE", "copyButtonLabel": r"קיבלתי!",
"copyButtonLabel": r"COPY", "cutButtonLabel": r"גזור",
"cutButtonLabel": r"CUT", "okButtonLabel": r"בסדר",
"okButtonLabel": r"OK", "pasteButtonLabel": r"הדבק",
"pasteButtonLabel": r"PASTE", "selectAllButtonLabel": r"בחר הכל",
"selectAllButtonLabel": r"SELECT ALL", "viewLicensesButtonLabel": r"ראה רישיונות"
"viewLicensesButtonLabel": r"VIEW LICENSES"
}, },
"de": const <String, String>{ "it": const <String, String>{
"openAppDrawerTooltip": r"Navigationsmenü öffnen", "timeOfDayFormat": r"HH:mm",
"backButtonTooltip": r"Zurück", "openAppDrawerTooltip": r"Apri il menu di navigazione",
"closeButtonTooltip": r"Schließen", "backButtonTooltip": r"Indietro",
"nextMonthTooltip": r"Nächster Monat", "closeButtonTooltip": r"Chiudi",
"previousMonthTooltip": r"Vorheriger Monat", "nextMonthTooltip": r"Il prossimo mese",
"nextPageTooltip": r"Nächste Seite", "previousMonthTooltip": r"Il mese scorso",
"previousPageTooltip": r"Vorherige Seite", "nextPageTooltip": r"Pagina successiva",
"showMenuTooltip": r"Menü anzeigen", "previousPageTooltip": r"Pagina precedente",
"licensesPageTitle": r"Lizenzen", "showMenuTooltip": r"Mostra il menu",
"pageRowsInfoTitle": r"$firstRow–$lastRow von $rowCount", "licensesPageTitle": r"Licenze",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow von etwa $rowCount", "pageRowsInfoTitle": r"$firstRow–$lastRow di $rowCount",
"rowsPerPageTitle": r"Zeilen pro Seite:", "pageRowsInfoTitleApproximate": r"$firstRow–$lastRow di circa $rowCount",
"selectedRowCountTitleZero": r"Keine Objekte ausgewählt", "rowsPerPageTitle": r"Righe per pagina:",
"selectedRowCountTitleOne": r"1 Objekt ausgewählt", "selectedRowCountTitleOther": r"$selectedRowCount selezionati",
"selectedRowCountTitleOther": r"$selectedRowCount Objekte ausgewählt", "cancelButtonLabel": r"ANNULLA",
"cancelButtonLabel": r"ABBRECHEN", "closeButtonLabel": r"CHIUDI",
"closeButtonLabel": r"SCHLIESSEN", "continueButtonLabel": r"CONTINUA",
"continueButtonLabel": r"FORTSETZEN", "copyButtonLabel": r"COPY",
"copyButtonLabel": r"KOPIEREN", "cutButtonLabel": r"TAGLIARE",
"cutButtonLabel": r"AUSSCHNEIDEN",
"okButtonLabel": r"OK", "okButtonLabel": r"OK",
"pasteButtonLabel": r"EINFÜGEN", "pasteButtonLabel": r"INCOLLA",
"selectAllButtonLabel": r"ALLES AUSWÄHLEN", "selectAllButtonLabel": r"SELEZIONA TUTTO",
"viewLicensesButtonLabel": r"LIZENZEN ANZEIGEN" "viewLicensesButtonLabel": r"VEDI LE LICENZE"
}, },
"ja": const <String, String>{ "ja": const <String, String>{
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"ナビゲーションメニューを開く", "openAppDrawerTooltip": r"ナビゲーションメニューを開く",
"backButtonTooltip": r"戻る", "backButtonTooltip": r"戻る",
"closeButtonTooltip": r"閉じる", "closeButtonTooltip": r"閉じる",
...@@ -355,7 +263,58 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String ...@@ -355,7 +263,58 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"selectAllButtonLabel": r"全選択", "selectAllButtonLabel": r"全選択",
"viewLicensesButtonLabel": r"ライセンス表記" "viewLicensesButtonLabel": r"ライセンス表記"
}, },
"ps": const <String, String>{
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"د پرانیستی نیینګ مینو",
"backButtonTooltip": r"شاته",
"closeButtonTooltip": r"بنده",
"nextMonthTooltip": r"بله میاشت",
"previousMonthTooltip": r"تیره میاشت",
"nextPageTooltip": r"بله پاڼه",
"previousPageTooltip": r"مخکینی مخ",
"showMenuTooltip": r"غورنۍ ښودل",
"licensesPageTitle": r"جوازونه",
"pageRowsInfoTitle": r"$firstRow–$lastRow د $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow څخه $rowCount د",
"rowsPerPageTitle": r"د هرې پاڼې پاڼې:",
"selectedRowCountTitleOther": r"$selectedRowCount توکي غوره شوي",
"cancelButtonLabel": r"لغوه کول",
"closeButtonLabel": r"تړل",
"continueButtonLabel": r"منځپانګې",
"copyButtonLabel": r"کاپی",
"cutButtonLabel": r"کم کړئ",
"okButtonLabel": r"سمه ده",
"pasteButtonLabel": r"پیټ کړئ",
"selectAllButtonLabel": r"غوره کړئ",
"viewLicensesButtonLabel": r"لیدلس وګورئ"
},
"pt": const <String, String>{
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"Abrir menu de navegação",
"backButtonTooltip": r"Costas",
"closeButtonTooltip": r"Fechar",
"nextMonthTooltip": r"Próximo mês",
"previousMonthTooltip": r"Mês anterior",
"nextPageTooltip": r"Próxima página",
"previousPageTooltip": r"Página anterior",
"showMenuTooltip": r"Mostrar menu",
"licensesPageTitle": r"Licenças",
"pageRowsInfoTitle": r"$firstRow–$lastRow de $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow de cerca de $rowCount",
"rowsPerPageTitle": r"Linhas por página:",
"selectedRowCountTitleOther": r"$selectedRowCount selecionados",
"cancelButtonLabel": r"CANCELAR",
"closeButtonLabel": r"FECHAR",
"continueButtonLabel": r"CONTINUAR",
"copyButtonLabel": r"CÓPIA DE",
"cutButtonLabel": r"CORTA",
"okButtonLabel": r"OK",
"pasteButtonLabel": r"COLAR",
"selectAllButtonLabel": r"SELECIONAR TUDO",
"viewLicensesButtonLabel": r"VER LICENÇAS"
},
"ru": const <String, String>{ "ru": const <String, String>{
"timeOfDayFormat": r"H:mm",
"openAppDrawerTooltip": r"Открыть меню навигации", "openAppDrawerTooltip": r"Открыть меню навигации",
"backButtonTooltip": r"назад", "backButtonTooltip": r"назад",
"closeButtonTooltip": r"Закрыть", "closeButtonTooltip": r"Закрыть",
...@@ -378,5 +337,85 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String ...@@ -378,5 +337,85 @@ const Map<String, Map<String, String>> localizations = const <String, Map<String
"pasteButtonLabel": r"Паст", "pasteButtonLabel": r"Паст",
"selectAllButtonLabel": r"Выбрать все", "selectAllButtonLabel": r"Выбрать все",
"viewLicensesButtonLabel": r"ПРОСМОТРЕТЬ ЛИЦЕНЗИИ" "viewLicensesButtonLabel": r"ПРОСМОТРЕТЬ ЛИЦЕНЗИИ"
},
"sd": const <String, String>{
"timeOfDayFormat": r"HH:mm",
"openAppDrawerTooltip": r"اوپن جي مينڊيٽ مينيو",
"backButtonTooltip": r"پوئتي",
"closeButtonTooltip": r"بند ڪريو",
"nextMonthTooltip": r"ايندڙ مهيني",
"previousMonthTooltip": r"پويون مهينو",
"nextPageTooltip": r"اڳيون پيج",
"previousPageTooltip": r"پويون صفحو",
"showMenuTooltip": r"ڏيکاريو",
"licensesPageTitle": r"لائسنس",
"pageRowsInfoTitle": r"$firstRow–$lastRow جي $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow کان $rowCount تقريبن",
"rowsPerPageTitle": r"رني پاسي وارو صفحو",
"selectedRowCountTitleOther": r"$selectedRowCount شيون چونڊيل",
"cancelButtonLabel": r"منسوخ ڪيو",
"closeButtonLabel": r"بند ڪريو",
"continueButtonLabel": r"جاري رکو",
"copyButtonLabel": r"ڪاپي",
"cutButtonLabel": r"پٽي",
"okButtonLabel": r"ٺيڪ آهي",
"pasteButtonLabel": r"پيسٽ ڪريو",
"selectAllButtonLabel": r"سڀ چونڊيو",
"viewLicensesButtonLabel": r"لائسنس ڏسو"
},
"ur": const <String, String>{
"timeOfDayFormat": r"h:mm a",
"openAppDrawerTooltip": r"کھولیں نیویگیشن مینو",
"backButtonTooltip": r"واپس",
"closeButtonTooltip": r"بند کریں",
"nextMonthTooltip": r"اگلا مھینہ",
"previousMonthTooltip": r"پچھلا مھینہ",
"nextPageTooltip": r"اگلا صفحہ",
"previousPageTooltip": r"سابقہ ​​صفحہ",
"showMenuTooltip": r"مینو دکھائیں",
"licensesPageTitle": r"لائسنس",
"pageRowsInfoTitle": r"$firstRow–$lastRow کے $rowCount",
"pageRowsInfoTitleApproximate": r"$firstRow–$lastRow میں سے $rowCount تقریبا",
"rowsPerPageTitle": r"رویوں فی صفحہ:",
"selectedRowCountTitleOther": r"$selectedRowCount منتخب کردہ اشیاء",
"cancelButtonLabel": r"منسوخ کریں",
"closeButtonLabel": r"بند کریں",
"continueButtonLabel": r"جاری رکھیں",
"copyButtonLabel": r"کاپی",
"cutButtonLabel": r"کاٹیں",
"okButtonLabel": r"ٹھیک ہے",
"pasteButtonLabel": r"چسپاں",
"selectAllButtonLabel": r"تکاپیمام منتخب کریں",
"viewLicensesButtonLabel": r"لائسنس دیکھیں",
"anteMeridiemAbbreviation": r"AM",
"postMeridiemAbbreviation": r"PM"
},
"zh": const <String, String>{
"timeOfDayFormat": r"ah:mm",
"openAppDrawerTooltip": r"打开导航菜单",
"backButtonTooltip": r"背部",
"closeButtonTooltip": r"关",
"nextMonthTooltip": r"-下月就29了。",
"previousMonthTooltip": r"前一个月",
"nextPageTooltip": r"下一页",
"previousPageTooltip": r"上一页",
"showMenuTooltip": r"显示菜单",
"licensesPageTitle": r"许可证",
"pageRowsInfoTitle": r"$rowCount中的$firstRow-$lastRow",
"pageRowsInfoTitleApproximate": r"约$rowCount中的$firstRow-$lastRow",
"rowsPerPageTitle": r"每页行数:",
"selectedRowCountTitleOther": r"$selectedRowCount行所选",
"cancelButtonLabel": r"取消",
"continueButtonLabel": r"继续",
"closeButtonLabel": r"关闭",
"copyButtonLabel": r"复制",
"cutButtonLabel": r"剪切",
"okButtonLabel": r"确定",
"pasteButtonLabel": r"粘贴",
"selectAllButtonLabel": r"全选",
"viewLicensesButtonLabel": r"查看许可证",
"anteMeridiemAbbreviation": r"上午",
"postMeridiemAbbreviation": r"下午"
} }
}; };
{ {
"timeOfDayFormat": "h:mm a",
"openAppDrawerTooltip": "افتح قائمة التنقل", "openAppDrawerTooltip": "افتح قائمة التنقل",
"backButtonTooltip": "الى الخلف", "backButtonTooltip": "الى الخلف",
"closeButtonTooltip": "إغلا", "closeButtonTooltip": "إغلا",
...@@ -20,5 +21,7 @@ ...@@ -20,5 +21,7 @@
"okButtonLabel": "حسنا", "okButtonLabel": "حسنا",
"pasteButtonLabel": "عجين", "pasteButtonLabel": "عجين",
"selectAllButtonLabel": "اختر الكل", "selectAllButtonLabel": "اختر الكل",
"viewLicensesButtonLabel": "عرض التراخيص" "viewLicensesButtonLabel": "عرض التراخيص",
"anteMeridiemAbbreviation": "ص",
"postMeridiemAbbreviation": "م"
} }
{ {
"timeOfDayFormat": "HH:mm",
"openAppDrawerTooltip": "Navigationsmenü öffnen", "openAppDrawerTooltip": "Navigationsmenü öffnen",
"backButtonTooltip": "Zurück", "backButtonTooltip": "Zurück",
"closeButtonTooltip": "Schließen", "closeButtonTooltip": "Schließen",
......
{ {
"timeOfDayFormat": "h:mm a",
"@timeOfDayFormat": {
"description": "The ICU 'Short Time' pattern, such as 'HH:mm', 'h:mm a', 'H:mm'. See: http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US",
"type": "text"
},
"openAppDrawerTooltip": "Open navigation menu", "openAppDrawerTooltip": "Open navigation menu",
"@openAppDrawerTooltip": { "@openAppDrawerTooltip": {
"description": "The tooltip for the leading AppBar menu (aka 'hamburger') button", "description": "The tooltip for the leading AppBar menu (aka 'hamburger') button",
...@@ -126,5 +132,17 @@ ...@@ -126,5 +132,17 @@
"@viewLicensesButtonLabel": { "@viewLicensesButtonLabel": {
"description": "The label for the about box's view licenses button.", "description": "The label for the about box's view licenses button.",
"type": "text" "type": "text"
},
"anteMeridiemAbbreviation": "AM",
"@anteMeridiemAbbreviation": {
"description": "The abbreviation for ante meridiem (before noon) shown in the time picker.",
"type": "text"
},
"postMeridiemAbbreviation": "PM",
"@postMeridiemAbbreviation": {
"description": "The abbreviation for post meridiem (after noon) shown in the time picker.",
"type": "text"
} }
} }
{ {
"timeOfDayFormat": "H:mm",
"openAppDrawerTooltip": "Abrir el menú de navegación", "openAppDrawerTooltip": "Abrir el menú de navegación",
"backButtonTooltip": "Espalda", "backButtonTooltip": "Espalda",
"closeButtonTooltip": "Cerrar", "closeButtonTooltip": "Cerrar",
......
{ {
"timeOfDayFormat": "H:mm",
"openAppDrawerTooltip": "منوی ناوبری را باز کنید", "openAppDrawerTooltip": "منوی ناوبری را باز کنید",
"backButtonTooltip": "بازگشت", "backButtonTooltip": "بازگشت",
"closeButtonTooltip": "بستن", "closeButtonTooltip": "بستن",
......
{ {
"timeOfDayFormat": "HH:mm",
"openAppDrawerTooltip": "Ouvrir le menu de navigation", "openAppDrawerTooltip": "Ouvrir le menu de navigation",
"backButtonTooltip": "Retour", "backButtonTooltip": "Retour",
"closeButtonTooltip": "Fermer", "closeButtonTooltip": "Fermer",
......
{ {
"timeOfDayFormat": "H:mm",
"openAppDrawerTooltip": "פתח תפריט ניווט", "openAppDrawerTooltip": "פתח תפריט ניווט",
"backButtonTooltip": "אחורה", "backButtonTooltip": "אחורה",
"closeButtonTooltip": "סגור", "closeButtonTooltip": "סגור",
......
{ {
"timeOfDayFormat": "HH:mm",
"openAppDrawerTooltip": "Apri il menu di navigazione", "openAppDrawerTooltip": "Apri il menu di navigazione",
"backButtonTooltip": "Indietro", "backButtonTooltip": "Indietro",
"closeButtonTooltip": "Chiudi", "closeButtonTooltip": "Chiudi",
......
{ {
"timeOfDayFormat": "H:mm",
"openAppDrawerTooltip": "ナビゲーションメニューを開く", "openAppDrawerTooltip": "ナビゲーションメニューを開く",
"backButtonTooltip": "戻る", "backButtonTooltip": "戻る",
"closeButtonTooltip": "閉じる", "closeButtonTooltip": "閉じる",
......
{ {
"timeOfDayFormat": "HH:mm",
"openAppDrawerTooltip": "د پرانیستی نیینګ مینو", "openAppDrawerTooltip": "د پرانیستی نیینګ مینو",
"backButtonTooltip": "شاته", "backButtonTooltip": "شاته",
"closeButtonTooltip": "بنده", "closeButtonTooltip": "بنده",
......
{ {
"timeOfDayFormat": "HH:mm",
"openAppDrawerTooltip": "Abrir menu de navegação", "openAppDrawerTooltip": "Abrir menu de navegação",
"backButtonTooltip": "Costas", "backButtonTooltip": "Costas",
"closeButtonTooltip": "Fechar", "closeButtonTooltip": "Fechar",
......
{ {
"timeOfDayFormat": "H:mm",
"openAppDrawerTooltip": "Открыть меню навигации", "openAppDrawerTooltip": "Открыть меню навигации",
"backButtonTooltip": "назад", "backButtonTooltip": "назад",
"closeButtonTooltip": "Закрыть", "closeButtonTooltip": "Закрыть",
......
{ {
"timeOfDayFormat": "HH:mm",
"openAppDrawerTooltip": "اوپن جي مينڊيٽ مينيو", "openAppDrawerTooltip": "اوپن جي مينڊيٽ مينيو",
"backButtonTooltip": "پوئتي", "backButtonTooltip": "پوئتي",
"closeButtonTooltip": "بند ڪريو", "closeButtonTooltip": "بند ڪريو",
......
{ {
"timeOfDayFormat": "h:mm a",
"openAppDrawerTooltip": "کھولیں نیویگیشن مینو", "openAppDrawerTooltip": "کھولیں نیویگیشن مینو",
"backButtonTooltip": "واپس", "backButtonTooltip": "واپس",
"closeButtonTooltip": "بند کریں", "closeButtonTooltip": "بند کریں",
...@@ -20,5 +21,7 @@ ...@@ -20,5 +21,7 @@
"okButtonLabel": "ٹھیک ہے", "okButtonLabel": "ٹھیک ہے",
"pasteButtonLabel": "چسپاں", "pasteButtonLabel": "چسپاں",
"selectAllButtonLabel": "تکاپیمام منتخب کریں", "selectAllButtonLabel": "تکاپیمام منتخب کریں",
"viewLicensesButtonLabel": "لائسنس دیکھیں" "viewLicensesButtonLabel": "لائسنس دیکھیں",
"anteMeridiemAbbreviation": "AM",
"postMeridiemAbbreviation": "PM"
} }
\ No newline at end of file
{ {
"timeOfDayFormat": "ah:mm",
"openAppDrawerTooltip": "打开导航菜单", "openAppDrawerTooltip": "打开导航菜单",
"backButtonTooltip": "返回", "backButtonTooltip": "返回",
"closeButtonTooltip": "关闭", "closeButtonTooltip": "关闭",
...@@ -20,5 +21,11 @@ ...@@ -20,5 +21,11 @@
"okButtonLabel": "确定", "okButtonLabel": "确定",
"pasteButtonLabel": "粘贴", "pasteButtonLabel": "粘贴",
"selectAllButtonLabel": "全选", "selectAllButtonLabel": "全选",
"viewLicensesButtonLabel": "查看许可证" "viewLicensesButtonLabel": "查看许可证",
"backButtonTooltip": "背部",
"closeButtonTooltip": "关",
"nextMonthTooltip": "-下月就29了。",
"previousMonthTooltip": "前一个月",
"anteMeridiemAbbreviation": "上午",
"postMeridiemAbbreviation": "下午"
} }
...@@ -10,7 +10,7 @@ import 'package:intl/intl.dart' as intl; ...@@ -10,7 +10,7 @@ import 'package:intl/intl.dart' as intl;
import 'i18n/localizations.dart'; import 'i18n/localizations.dart';
/// Defines the localized resource values used by the Material widgts. /// Defines the localized resource values used by the Material widgets.
/// ///
/// See also: /// See also:
/// ///
...@@ -80,6 +80,18 @@ abstract class MaterialLocalizations { ...@@ -80,6 +80,18 @@ abstract class MaterialLocalizations {
/// Label for the [AboutBox] button that shows the [LicensePage]. /// Label for the [AboutBox] button that shows the [LicensePage].
String get viewLicensesButtonLabel; String get viewLicensesButtonLabel;
/// The abbreviation for ante meridiem (before noon) shown in the time picker.
String get anteMeridiemAbbreviation;
/// The abbreviation for post meridiem (after noon) shown in the time picker.
String get postMeridiemAbbreviation;
/// The format used to lay out the time picker.
///
/// The documentation for [TimeOfDayFormat] enum values provides details on
/// each supported layout.
TimeOfDayFormat get timeOfDayFormat;
/// The `MaterialLocalizations` from the closest [Localizations] instance /// The `MaterialLocalizations` from the closest [Localizations] instance
/// that encloses the given context. /// that encloses the given context.
/// ///
...@@ -99,25 +111,30 @@ abstract class MaterialLocalizations { ...@@ -99,25 +111,30 @@ abstract class MaterialLocalizations {
/// Localized strings for the material widgets. /// Localized strings for the material widgets.
class DefaultMaterialLocalizations implements MaterialLocalizations { class DefaultMaterialLocalizations implements MaterialLocalizations {
/// Construct an object that defines the material widgets' localized strings /// Constructs an object that defines the material widgets' localized strings
/// for the given `locale`. /// for the given `locale`.
/// ///
/// [LocalizationsDelegate] implementations typically call the static [load] /// [LocalizationsDelegate] implementations typically call the static [load]
/// function, rather than constructing this class directly. /// function, rather than constructing this class directly.
DefaultMaterialLocalizations(this.locale) { factory DefaultMaterialLocalizations(Locale locale) {
assert(locale != null); assert(locale != null);
_nameToValue = localizations[_localeName]
?? localizations[locale.languageCode] final Map<String, String> result = <String, String>{};
?? localizations['en'] if (localizations.containsKey(locale.languageCode))
?? <String, String>{}; result.addAll(localizations[locale.languageCode]);
if (localizations.containsKey(locale.toString()))
result.addAll(localizations[locale.toString()]);
return new DefaultMaterialLocalizations._(locale, result);
} }
Map<String, String> _nameToValue; DefaultMaterialLocalizations._(this.locale, this._nameToValue);
/// The locale for which the values of this class's localized resources /// The locale for which the values of this class's localized resources
/// have been translated. /// have been translated.
final Locale locale; final Locale locale;
final Map<String, String> _nameToValue;
String get _localeName { String get _localeName {
final String localeName = locale.countryCode.isEmpty ? locale.languageCode : locale.toString(); final String localeName = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
return intl.Intl.canonicalizedLocale(localeName); return intl.Intl.canonicalizedLocale(localeName);
...@@ -224,6 +241,47 @@ class DefaultMaterialLocalizations implements MaterialLocalizations { ...@@ -224,6 +241,47 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
@override @override
String get viewLicensesButtonLabel => _nameToValue['viewLicensesButtonLabel']; String get viewLicensesButtonLabel => _nameToValue['viewLicensesButtonLabel'];
@override
String get anteMeridiemAbbreviation => _nameToValue['anteMeridiemAbbreviation'];
@override
String get postMeridiemAbbreviation => _nameToValue['postMeridiemAbbreviation'];
/// The [TimeOfDayFormat] corresponding to one of the following supported
/// patterns:
///
/// * `HH:mm`
/// * `HH.mm`
/// * `HH 'h' mm`
/// * `HH:mm น.`
/// * `H:mm`
/// * `h:mm a`
/// * `a h:mm`
/// * `ah:mm`
///
/// See also:
///
/// * http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US shows the
/// short time pattern used in locale en_US
@override
TimeOfDayFormat get timeOfDayFormat {
final String icuShortTimePattern = _nameToValue['timeOfDayFormat'];
assert(() {
if (!_icuTimeOfDayToEnum.containsKey(icuShortTimePattern)) {
throw new FlutterError(
'"$icuShortTimePattern" is not one of the ICU short time patterns '
'supported by the material library. Here is the list of supported '
'patterns:\n ' +
_icuTimeOfDayToEnum.keys.join('\n ')
);
}
return true;
});
return _icuTimeOfDayToEnum[icuShortTimePattern];
}
/// Creates an object that provides localized resource values for the /// Creates an object that provides localized resource values for the
/// for the widgets of the material library. /// for the widgets of the material library.
/// ///
...@@ -233,3 +291,66 @@ class DefaultMaterialLocalizations implements MaterialLocalizations { ...@@ -233,3 +291,66 @@ class DefaultMaterialLocalizations implements MaterialLocalizations {
return new SynchronousFuture<MaterialLocalizations>(new DefaultMaterialLocalizations(locale)); return new SynchronousFuture<MaterialLocalizations>(new DefaultMaterialLocalizations(locale));
} }
} }
const Map<String, TimeOfDayFormat> _icuTimeOfDayToEnum = const <String, TimeOfDayFormat>{
'HH:mm': TimeOfDayFormat.HH_colon_mm,
'HH.mm': TimeOfDayFormat.HH_dot_mm,
"HH 'h' mm": TimeOfDayFormat.frenchCanadian,
'HH:mm น.': TimeOfDayFormat.HH_colon_mm,
'H:mm': TimeOfDayFormat.H_colon_mm,
'h:mm a': TimeOfDayFormat.h_colon_mm_space_a,
'a h:mm': TimeOfDayFormat.a_space_h_colon_mm,
'ah:mm': TimeOfDayFormat.a_space_h_colon_mm,
};
/// Determines how the time picker invoked using [showTimePicker] formats and
/// lays out the time controls.
///
/// The time picker provides layout configurations optimized for each of the
/// enum values.
enum TimeOfDayFormat {
/// Corresponds to the ICU 'HH:mm' pattern.
///
/// This format uses 24-hour two-digit zero-padded hours. Controls are always
/// laid out horizontally. Hours are separated from minutes by one colon
/// character.
HH_colon_mm,
/// Corresponds to the ICU 'HH.mm' pattern.
///
/// This format uses 24-hour two-digit zero-padded hours. Controls are always
/// laid out horizontally. Hours are separated from minutes by one dot
/// character.
HH_dot_mm,
/// Corresponds to the ICU "HH 'h' mm" pattern used in Canadian French.
///
/// This format uses 24-hour two-digit zero-padded hours. Controls are always
/// laid out horizontally. Hours are separated from minutes by letter 'h'.
frenchCanadian,
/// Corresponds to the ICU 'H:mm' pattern.
///
/// This format uses 24-hour non-padded variable-length hours. Controls are
/// always laid out horizontally. Hours are separated from minutes by one
/// colon character.
H_colon_mm,
/// Corresponds to the ICU 'h:mm a' pattern.
///
/// This format uses 12-hour non-padded variable-length hours with a day
/// period. Controls are laid out horizontally in portrait mode. In landscape
/// mode, the day period appears vertically after (consistent with the ambient
/// [TextDirection]) hour-minute indicator. Hours are separated from minutes
/// by one colon character.
h_colon_mm_space_a,
/// Corresponds to the ICU 'a h:mm' pattern.
///
/// This format uses 12-hour non-padded variable-length hours with a day
/// period. Controls are laid out horizontally in portrait mode. In landscape
/// mode, the day period appears vertically before (consistent with the
/// ambient [TextDirection]) hour-minute indicator. Hours are separated from
/// minutes by one colon character.
a_space_h_colon_mm,
}
...@@ -35,7 +35,7 @@ enum DayPeriod { ...@@ -35,7 +35,7 @@ enum DayPeriod {
pm, pm,
} }
/// A value representing a time during the day /// A value representing a time during the day.
@immutable @immutable
class TimeOfDay { class TimeOfDay {
/// Creates a time of day. /// Creates a time of day.
...@@ -89,16 +89,12 @@ class TimeOfDay { ...@@ -89,16 +89,12 @@ class TimeOfDay {
/// A string representing the hour of the current period (e.g., '4' or '6'). /// A string representing the hour of the current period (e.g., '4' or '6').
String get hourOfPeriodLabel { String get hourOfPeriodLabel {
// TODO(ianh): Localize.
final int hourOfPeriod = this.hourOfPeriod; final int hourOfPeriod = this.hourOfPeriod;
if (hourOfPeriod == 0) if (hourOfPeriod == 0)
return '12'; return '12';
return hourOfPeriod.toString(); return hourOfPeriod.toString();
} }
/// A string representing the current period (e.g., 'a.m.').
String get periodLabel => period == DayPeriod.am ? 'a.m.' : 'p.m.'; // TODO(ianh): Localize.
/// The hour at which the current period starts. /// The hour at which the current period starts.
int get periodOffset => period == DayPeriod.am ? 0 : _kHoursPerPeriod; int get periodOffset => period == DayPeriod.am ? 0 : _kHoursPerPeriod;
...@@ -114,9 +110,8 @@ class TimeOfDay { ...@@ -114,9 +110,8 @@ class TimeOfDay {
@override @override
int get hashCode => hashValues(hour, minute); int get hashCode => hashValues(hour, minute);
// TODO(ianh): Localize.
@override @override
String toString() => '$hourOfPeriodLabel:$minuteLabel $periodLabel'; String toString() => '$hourLabel:$minuteLabel';
} }
enum _TimePickerMode { hour, minute } enum _TimePickerMode { hour, minute }
...@@ -130,84 +125,544 @@ const double _kTimePickerWidthLandscape = 512.0; ...@@ -130,84 +125,544 @@ const double _kTimePickerWidthLandscape = 512.0;
const double _kTimePickerHeightPortrait = 484.0; const double _kTimePickerHeightPortrait = 484.0;
const double _kTimePickerHeightLandscape = 304.0; const double _kTimePickerHeightLandscape = 304.0;
/// The horizontal gap between the day period fragment and the fragment
/// positioned next to it horizontally.
///
/// Normally there's only one horizontal sibling, and it may appear on the left
/// or right depending on the current [TextDirection].
const double _kPeriodGap = 8.0; const double _kPeriodGap = 8.0;
/// The vertical gap between pieces when laid out vertically (in portrait mode).
const double _kVerticalGap = 8.0;
enum _TimePickerHeaderId { enum _TimePickerHeaderId {
hour, hour,
colon, colon,
minute, minute,
period, // AM/PM picker period, // AM/PM picker
dot,
hString, // French Canadian "h" literal
}
/// Provides properties for rendering time picker header fragments.
@immutable
class _TimePickerFragmentContext {
const _TimePickerFragmentContext({
@required this.headerTextTheme,
@required this.textDirection,
@required this.selectedTime,
@required this.mode,
@required this.activeColor,
@required this.activeStyle,
@required this.inactiveColor,
@required this.inactiveStyle,
@required this.onTimeChange,
@required this.onModeChange,
}) : assert(headerTextTheme != null),
assert(textDirection != null),
assert(selectedTime != null),
assert(mode != null),
assert(activeColor != null),
assert(activeStyle != null),
assert(inactiveColor != null),
assert(inactiveStyle != null),
assert(onTimeChange != null),
assert(onModeChange != null);
final TextTheme headerTextTheme;
final TextDirection textDirection;
final TimeOfDay selectedTime;
final _TimePickerMode mode;
final Color activeColor;
final TextStyle activeStyle;
final Color inactiveColor;
final TextStyle inactiveStyle;
final ValueChanged<TimeOfDay> onTimeChange;
final ValueChanged<_TimePickerMode> onModeChange;
}
/// Describes how hours are formatted.
enum _TimePickerHourFormat {
/// Zero-padded two-digit 24-hour format ranging from "00" to "23".
HH,
/// Non-padded variable-length 24-hour format ranging from "0" to "23".
H,
/// Non-padded variable-length hour in day period format ranging from "1" to
/// "12".
h,
}
/// Contains the [widget] and layout properties of an atom of time information,
/// such as am/pm indicator, hour, minute and string literals appearing in the
/// formatted time string.
class _TimePickerHeaderFragment {
const _TimePickerHeaderFragment({
@required this.layoutId,
@required this.widget,
this.startMargin: 0.0,
}) : assert(layoutId != null),
assert(widget != null),
assert(startMargin != null);
/// Identifier used by the custom layout to refer to the widget.
final _TimePickerHeaderId layoutId;
/// The widget that renders a piece of time information.
final Widget widget;
/// Horizontal distance from the fragment appearing at the start of this
/// fragment.
///
/// This value contributes to the total horizontal width of all fragments
/// appearing on the same line, unless it is the first fragment on the line,
/// in which case this value is ignored.
final double startMargin;
}
/// An unbreakable part of the time picker header.
///
/// When the picker is laid out vertically, [fragments] of the piece are laid
/// out on the same line, with each piece getting its own line.
class _TimePickerHeaderPiece {
/// Creates a time picker header piece.
///
/// All arguments must be non-null. If the piece does not contain a pivot
/// fragment, use the value -1 as a convention.
const _TimePickerHeaderPiece(this.pivotIndex, this.fragments, { this.bottomMargin: 0.0 })
: assert(pivotIndex != null),
assert(fragments != null),
assert(bottomMargin != null);
/// Index into the [fragments] list, pointing at the fragment that's centered
/// horizontally.
final int pivotIndex;
/// Fragments this piece is made of.
final List<_TimePickerHeaderFragment> fragments;
/// Vertical distance between this piece and the next piece.
///
/// This property applies only when the header is laid out vertically.
final double bottomMargin;
}
/// Describes how the time picker header must be formatted.
///
/// A [_TimePickerHeaderFormat] is made of multiple [_TimePickerHeaderPiece]s.
/// A piece is made of multiple [_TimePickerHeaderFragment]s. A fragment has a
/// widget used to render some time information and contains some layout
/// properties.
///
/// ## Layout rules
///
/// Pieces are laid out such that all fragments inside the same piece are laid
/// out horizontally. Pieces are laid out horizontally if portrait orientation,
/// and vertically in landscape orientation.
///
/// One of the pieces is identified as a _centrepiece_. It is a piece that is
/// positioned in the center of the header, with all other pieces positioned
/// to the left or right of it.
class _TimePickerHeaderFormat {
const _TimePickerHeaderFormat(this.centrepieceIndex, this.pieces)
: assert(centrepieceIndex != null),
assert(pieces != null);
/// Index into the [pieces] list pointing at the piece that contains the
/// pivot fragment.
final int centrepieceIndex;
/// Pieces that constitute a time picker header.
final List<_TimePickerHeaderPiece> pieces;
}
/// Displays the am/pm fragment and provides controls for switching between am
/// and pm.
class _DayPeriodControl extends StatelessWidget {
const _DayPeriodControl({
@required this.fragmentContext,
});
final _TimePickerFragmentContext fragmentContext;
void _handleChangeDayPeriod() {
final int newHour = (fragmentContext.selectedTime.hour + _kHoursPerPeriod) % _kHoursPerDay;
fragmentContext.onTimeChange(fragmentContext.selectedTime.replacing(hour: newHour));
}
@override
Widget build(BuildContext context) {
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(context);
final TextTheme headerTextTheme = fragmentContext.headerTextTheme;
final TimeOfDay selectedTime = fragmentContext.selectedTime;
final Color activeColor = fragmentContext.activeColor;
final Color inactiveColor = fragmentContext.inactiveColor;
final TextStyle amStyle = headerTextTheme.subhead.copyWith(
color: selectedTime.period == DayPeriod.am ? activeColor: inactiveColor
);
final TextStyle pmStyle = headerTextTheme.subhead.copyWith(
color: selectedTime.period == DayPeriod.pm ? activeColor: inactiveColor
);
return new GestureDetector(
onTap: Feedback.wrapForTap(_handleChangeDayPeriod, context),
behavior: HitTestBehavior.opaque,
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text(materialLocalizations.anteMeridiemAbbreviation, style: amStyle),
const SizedBox(width: 0.0, height: 4.0), // Vertical spacer
new Text(materialLocalizations.postMeridiemAbbreviation, style: pmStyle),
],
),
);
}
}
/// Displays the hour fragment.
///
/// When tapped changes time picker dial mode to [_TimePickerMode.hour].
class _HourControl extends StatelessWidget {
const _HourControl({
@required this.fragmentContext,
@required this.hourFormat,
});
final _TimePickerFragmentContext fragmentContext;
final _TimePickerHourFormat hourFormat;
@override
Widget build(BuildContext context) {
final TextStyle hourStyle = fragmentContext.mode == _TimePickerMode.hour
? fragmentContext.activeStyle
: fragmentContext.inactiveStyle;
return new GestureDetector(
onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.hour), context),
child: new Text(_formatHour(), style: hourStyle),
);
}
String _formatHour() {
assert(hourFormat != null);
switch (hourFormat) {
case _TimePickerHourFormat.HH:
return fragmentContext.selectedTime.hourLabel;
case _TimePickerHourFormat.H:
return fragmentContext.selectedTime.hour.toString();
case _TimePickerHourFormat.h:
return fragmentContext.selectedTime.hourOfPeriodLabel;
}
return null;
}
}
/// A passive fragment showing a string value.
class _StringFragment extends StatelessWidget {
const _StringFragment({
@required this.fragmentContext,
@required this.value,
});
final _TimePickerFragmentContext fragmentContext;
final String value;
@override
Widget build(BuildContext context) {
return new Text(value, style: fragmentContext.inactiveStyle);
}
}
/// Displays the minute fragment.
///
/// When tapped changes time picker dial mode to [_TimePickerMode.minute].
class _MinuteControl extends StatelessWidget {
const _MinuteControl({
@required this.fragmentContext,
});
final _TimePickerFragmentContext fragmentContext;
@override
Widget build(BuildContext context) {
final TextStyle minuteStyle = fragmentContext.mode == _TimePickerMode.minute
? fragmentContext.activeStyle
: fragmentContext.inactiveStyle;
return new GestureDetector(
onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.minute), context),
child: new Text(fragmentContext.selectedTime.minuteLabel, style: minuteStyle),
);
}
}
_TimePickerHourFormat _getHourFormat(TimeOfDayFormat format) {
switch (format) {
case TimeOfDayFormat.h_colon_mm_space_a:
case TimeOfDayFormat.a_space_h_colon_mm:
return _TimePickerHourFormat.h;
case TimeOfDayFormat.H_colon_mm:
return _TimePickerHourFormat.H;
case TimeOfDayFormat.HH_dot_mm:
case TimeOfDayFormat.HH_colon_mm:
case TimeOfDayFormat.frenchCanadian:
return _TimePickerHourFormat.HH;
}
return null;
}
/// Provides time picker header layout configuration for the given
/// [timeOfDayFormat] passing [context] to each widget in the configuration.
///
/// [timeOfDayFormat] and [context] must not be `null`.
_TimePickerHeaderFormat _buildHeaderFormat(TimeOfDayFormat timeOfDayFormat, _TimePickerFragmentContext context) {
// Creates an hour fragment.
_TimePickerHeaderFragment hour(_TimePickerHourFormat hourFormat) {
return new _TimePickerHeaderFragment(
layoutId: _TimePickerHeaderId.hour,
widget: new _HourControl(fragmentContext: context, hourFormat: hourFormat),
startMargin: _kPeriodGap,
);
}
// Creates a minute fragment.
_TimePickerHeaderFragment minute() {
return new _TimePickerHeaderFragment(
layoutId: _TimePickerHeaderId.minute,
widget: new _MinuteControl(fragmentContext: context),
);
}
// Creates a string fragment.
_TimePickerHeaderFragment string(_TimePickerHeaderId layoutId, String value) {
return new _TimePickerHeaderFragment(
layoutId: layoutId,
widget: new _StringFragment(
fragmentContext: context,
value: value,
),
);
}
// Creates an am/pm fragment.
_TimePickerHeaderFragment dayPeriod() {
return new _TimePickerHeaderFragment(
layoutId: _TimePickerHeaderId.period,
widget: new _DayPeriodControl(fragmentContext: context),
startMargin: _kPeriodGap,
);
}
// Convenience function for creating a time header format with up to two pieces.
_TimePickerHeaderFormat format(int centrepieceIndex, _TimePickerHeaderPiece piece1,
[ _TimePickerHeaderPiece piece2 ]) {
final List<_TimePickerHeaderPiece> pieces = <_TimePickerHeaderPiece>[];
switch (context.textDirection) {
case TextDirection.ltr:
pieces.add(piece1);
if (piece2 != null)
pieces.add(piece2);
break;
case TextDirection.rtl:
if (piece2 != null)
pieces.add(piece2);
pieces.add(piece1);
centrepieceIndex = pieces.length - centrepieceIndex - 1;
break;
}
return new _TimePickerHeaderFormat(centrepieceIndex, pieces);
}
// Convenience function for creating a time header piece with up to three fragments.
_TimePickerHeaderPiece piece({ int pivotIndex: -1, double bottomMargin: 0.0,
_TimePickerHeaderFragment fragment1, _TimePickerHeaderFragment fragment2, _TimePickerHeaderFragment fragment3 }) {
final List<_TimePickerHeaderFragment> fragments = <_TimePickerHeaderFragment>[fragment1];
if (fragment2 != null) {
fragments.add(fragment2);
if (fragment3 != null)
fragments.add(fragment3);
}
return new _TimePickerHeaderPiece(pivotIndex, fragments, bottomMargin: bottomMargin);
}
switch (timeOfDayFormat) {
case TimeOfDayFormat.h_colon_mm_space_a:
return format(
0,
piece(
pivotIndex: 1,
fragment1: hour(_TimePickerHourFormat.h),
fragment2: string(_TimePickerHeaderId.colon, ':'),
fragment3: minute(),
),
piece(
bottomMargin: _kVerticalGap,
fragment1: dayPeriod(),
),
);
case TimeOfDayFormat.H_colon_mm:
return format(0, piece(
pivotIndex: 1,
fragment1: hour(_TimePickerHourFormat.H),
fragment2: string(_TimePickerHeaderId.colon, ':'),
fragment3: minute(),
));
case TimeOfDayFormat.HH_dot_mm:
return format(0, piece(
pivotIndex: 1,
fragment1: hour(_TimePickerHourFormat.HH),
fragment2: string(_TimePickerHeaderId.dot, '.'),
fragment3: minute(),
));
case TimeOfDayFormat.a_space_h_colon_mm:
return format(
1,
piece(
bottomMargin: _kVerticalGap,
fragment1: dayPeriod(),
),
piece(
pivotIndex: 1,
fragment1: hour(_TimePickerHourFormat.h),
fragment2: string(_TimePickerHeaderId.colon, ':'),
fragment3: minute(),
),
);
case TimeOfDayFormat.frenchCanadian:
return format(0, piece(
pivotIndex: 1,
fragment1: hour(_TimePickerHourFormat.HH),
fragment2: string(_TimePickerHeaderId.hString, 'h'),
fragment3: minute(),
));
case TimeOfDayFormat.HH_colon_mm:
return format(0, piece(
pivotIndex: 1,
fragment1: hour(_TimePickerHourFormat.HH),
fragment2: string(_TimePickerHeaderId.colon, ':'),
fragment3: minute(),
));
}
return null;
} }
class _TimePickerHeaderLayout extends MultiChildLayoutDelegate { class _TimePickerHeaderLayout extends MultiChildLayoutDelegate {
_TimePickerHeaderLayout(this.orientation); _TimePickerHeaderLayout(this.orientation, this.format)
: assert(orientation != null),
assert(format != null);
final Orientation orientation; final Orientation orientation;
final _TimePickerHeaderFormat format;
@override @override
void performLayout(Size size) { void performLayout(Size size) {
final BoxConstraints constraints = new BoxConstraints.loose(size); final BoxConstraints constraints = new BoxConstraints.loose(size);
final Size hourSize = layoutChild(_TimePickerHeaderId.hour, constraints);
final Size colonSize = layoutChild(_TimePickerHeaderId.colon, constraints);
final Size minuteSize = layoutChild(_TimePickerHeaderId.minute, constraints);
final Size periodSize = layoutChild(_TimePickerHeaderId.period, constraints);
switch (orientation) { switch (orientation) {
// 11:57--period
//
// The colon is centered horizontally, the entire layout is centered vertically.
// The "--" is a _kPeriodGap horizontal gap.
case Orientation.portrait: case Orientation.portrait:
final double width = colonSize.width / 2.0 + minuteSize.width + _kPeriodGap + periodSize.width; _layoutHorizontally(size, constraints);
final double right = math.max(0.0, size.width / 2.0 - width); break;
case Orientation.landscape:
_layoutVertically(size, constraints);
break;
}
}
double x = size.width - right - periodSize.width; void _layoutHorizontally(Size size, BoxConstraints constraints) {
positionChild(_TimePickerHeaderId.period, new Offset(x, (size.height - periodSize.height) / 2.0)); final List<_TimePickerHeaderFragment> fragmentsFlattened = <_TimePickerHeaderFragment>[];
final Map<_TimePickerHeaderId, Size> childSizes = <_TimePickerHeaderId, Size>{};
int pivotIndex = 0;
for (int pieceIndex = 0; pieceIndex < format.pieces.length; pieceIndex += 1) {
final _TimePickerHeaderPiece piece = format.pieces[pieceIndex];
for (final _TimePickerHeaderFragment fragment in piece.fragments) {
childSizes[fragment.layoutId] = layoutChild(fragment.layoutId, constraints);
fragmentsFlattened.add(fragment);
}
x -= minuteSize.width + _kPeriodGap; if (pieceIndex == format.centrepieceIndex)
positionChild(_TimePickerHeaderId.minute, new Offset(x, (size.height - minuteSize.height) / 2.0)); pivotIndex += format.pieces[format.centrepieceIndex].pivotIndex;
else if (pieceIndex < format.centrepieceIndex)
pivotIndex += piece.fragments.length;
}
x -= colonSize.width; _positionPivoted(size.width, size.height / 2.0, childSizes, fragmentsFlattened, pivotIndex);
positionChild(_TimePickerHeaderId.colon, new Offset(x, (size.height - colonSize.height) / 2.0)); }
x -= hourSize.width; void _layoutVertically(Size size, BoxConstraints constraints) {
positionChild(_TimePickerHeaderId.hour, new Offset(x, (size.height - hourSize.height) / 2.0)); final Map<_TimePickerHeaderId, Size> childSizes = <_TimePickerHeaderId, Size>{};
break; final List<double> pieceHeights = <double>[];
double height = 0.0;
double margin = 0.0;
for (final _TimePickerHeaderPiece piece in format.pieces) {
double pieceHeight = 0.0;
for (final _TimePickerHeaderFragment fragment in piece.fragments) {
final Size childSize = childSizes[fragment.layoutId] = layoutChild(fragment.layoutId, constraints);
pieceHeight = math.max(pieceHeight, childSize.height);
}
pieceHeights.add(pieceHeight);
height += pieceHeight + margin;
// Delay application of margin until next piece because margin of the
// bottom-most piece should not contribute to the size.
margin = piece.bottomMargin;
}
// 11:57 final _TimePickerHeaderPiece centrepiece = format.pieces[format.centrepieceIndex];
// -- double y = (size.height - height) / 2.0;
// period for (int pieceIndex = 0; pieceIndex < format.pieces.length; pieceIndex += 1) {
// if (pieceIndex != format.centrepieceIndex)
// The colon is centered horizontally, the entire layout is centered vertically. _positionPiece(size.width, y, childSizes, format.pieces[pieceIndex].fragments);
// The "--" is a _kPeriodGap vertical gap. else
case Orientation.landscape: _positionPivoted(size.width, y, childSizes, centrepiece.fragments, centrepiece.pivotIndex);
final double width = colonSize.width / 2.0 + minuteSize.width;
final double offset = math.max(0.0, size.width / 2.0 - width);
final double timeHeight = math.max(hourSize.height, colonSize.height);
final double height = timeHeight + _kPeriodGap + periodSize.height;
final double timeCenter = (size.height - height) / 2.0 + timeHeight / 2.0;
double x = size.width - offset - minuteSize.width; y += pieceHeights[pieceIndex] + format.pieces[pieceIndex].bottomMargin;
positionChild(_TimePickerHeaderId.minute, new Offset(x, timeCenter - minuteSize.height / 2.0)); }
}
x -= colonSize.width; void _positionPivoted(double width, double y, Map<_TimePickerHeaderId, Size> childSizes, List<_TimePickerHeaderFragment> fragments, int pivotIndex) {
positionChild(_TimePickerHeaderId.colon, new Offset(x, timeCenter - colonSize.height / 2.0)); double tailWidth = childSizes[fragments[pivotIndex].layoutId].width / 2.0;
for (_TimePickerHeaderFragment fragment in fragments.skip(pivotIndex + 1)) {
tailWidth += childSizes[fragment.layoutId].width + fragment.startMargin;
}
x -= hourSize.width; double x = width / 2.0 + tailWidth;
positionChild(_TimePickerHeaderId.hour, new Offset(x, timeCenter - hourSize.height / 2.0)); x = math.min(x, width);
for (int i = fragments.length - 1; i >= 0; i -= 1) {
final _TimePickerHeaderFragment fragment = fragments[i];
final Size childSize = childSizes[fragment.layoutId];
x -= childSize.width;
positionChild(fragment.layoutId, new Offset(x, y - childSize.height / 2.0));
x -= fragment.startMargin;
}
}
x = (size.width - periodSize.width) / 2.0; void _positionPiece(double width, double centeredAroundY, Map<_TimePickerHeaderId, Size> childSizes, List<_TimePickerHeaderFragment> fragments) {
positionChild(_TimePickerHeaderId.period, new Offset(x, timeCenter + timeHeight / 2.0 + _kPeriodGap)); double pieceWidth = 0.0;
break; double nextMargin = 0.0;
for (_TimePickerHeaderFragment fragment in fragments) {
final Size childSize = childSizes[fragment.layoutId];
pieceWidth += childSize.width + nextMargin;
// Delay application of margin until next element because margin of the
// left-most fragment should not contribute to the size.
nextMargin = fragment.startMargin;
}
double x = (width + pieceWidth) / 2.0;
for (int i = fragments.length - 1; i >= 0; i -= 1) {
final _TimePickerHeaderFragment fragment = fragments[i];
final Size childSize = childSizes[fragment.layoutId];
x -= childSize.width;
positionChild(fragment.layoutId, new Offset(x, centeredAroundY - childSize.height / 2.0));
x -= fragment.startMargin;
} }
} }
@override @override
bool shouldRelayout(_TimePickerHeaderLayout oldDelegate) => orientation != oldDelegate.orientation; bool shouldRelayout(_TimePickerHeaderLayout oldDelegate) => orientation != oldDelegate.orientation || format != oldDelegate.format;
} }
// TODO(ianh): Localize!
class _TimePickerHeader extends StatelessWidget { class _TimePickerHeader extends StatelessWidget {
const _TimePickerHeader({ const _TimePickerHeader({
@required this.selectedTime, @required this.selectedTime,
...@@ -230,11 +685,6 @@ class _TimePickerHeader extends StatelessWidget { ...@@ -230,11 +685,6 @@ class _TimePickerHeader extends StatelessWidget {
onModeChanged(value); onModeChanged(value);
} }
void _handleChangeDayPeriod() {
final int newHour = (selectedTime.hour + _kHoursPerPeriod) % _kHoursPerDay;
onChanged(selectedTime.replacing(hour: newHour));
}
TextStyle _getBaseHeaderStyle(TextTheme headerTextTheme) { TextStyle _getBaseHeaderStyle(TextTheme headerTextTheme) {
// These font sizes aren't listed in the spec explicitly. I worked them out // These font sizes aren't listed in the spec explicitly. I worked them out
// by measuring the text using a screen ruler and comparing them to the // by measuring the text using a screen ruler and comparing them to the
...@@ -252,18 +702,21 @@ class _TimePickerHeader extends StatelessWidget { ...@@ -252,18 +702,21 @@ class _TimePickerHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final TextTheme headerTextTheme = themeData.primaryTextTheme; final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat;
final TextStyle baseHeaderStyle = _getBaseHeaderStyle(headerTextTheme);
Color activeColor; EdgeInsets padding;
Color inactiveColor; double height;
switch(themeData.primaryColorBrightness) { double width;
case Brightness.light:
activeColor = Colors.black87; assert(orientation != null);
inactiveColor = Colors.black54; switch (orientation) {
case Orientation.portrait:
height = _kTimePickerHeaderPortraitHeight;
padding = const EdgeInsets.symmetric(horizontal: 24.0);
break; break;
case Brightness.dark: case Orientation.landscape:
activeColor = Colors.white; width = _kTimePickerHeaderLandscapeWidth;
inactiveColor = Colors.white70; padding = const EdgeInsets.symmetric(horizontal: 16.0);
break; break;
} }
...@@ -277,73 +730,52 @@ class _TimePickerHeader extends StatelessWidget { ...@@ -277,73 +730,52 @@ class _TimePickerHeader extends StatelessWidget {
break; break;
} }
final TextStyle activeStyle = baseHeaderStyle.copyWith(color: activeColor); Color activeColor;
final TextStyle inactiveStyle = baseHeaderStyle.copyWith(color: inactiveColor); Color inactiveColor;
switch (themeData.primaryColorBrightness) {
final TextStyle hourStyle = mode == _TimePickerMode.hour ? activeStyle : inactiveStyle; case Brightness.light:
final TextStyle minuteStyle = mode == _TimePickerMode.minute ? activeStyle : inactiveStyle; activeColor = Colors.black87;
inactiveColor = Colors.black54;
final TextStyle amStyle = headerTextTheme.subhead.copyWith(
color: selectedTime.period == DayPeriod.am ? activeColor: inactiveColor
);
final TextStyle pmStyle = headerTextTheme.subhead.copyWith(
color: selectedTime.period == DayPeriod.pm ? activeColor: inactiveColor
);
final Widget dayPeriodPicker = new GestureDetector(
onTap: Feedback.wrapForTap(_handleChangeDayPeriod, context),
behavior: HitTestBehavior.opaque,
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text('AM', style: amStyle),
const SizedBox(width: 0.0, height: 4.0), // Vertical spacer
new Text('PM', style: pmStyle),
]
)
);
final Widget hour = new GestureDetector(
onTap: Feedback.wrapForTap(() => _handleChangeMode(_TimePickerMode.hour), context),
child: new Text(selectedTime.hourOfPeriodLabel, style: hourStyle),
);
final Widget minute = new GestureDetector(
onTap: Feedback.wrapForTap(() => _handleChangeMode(_TimePickerMode.minute), context),
child: new Text(selectedTime.minuteLabel, style: minuteStyle),
);
final Widget colon = new Text(':', style: inactiveStyle);
EdgeInsets padding;
double height;
double width;
assert(orientation != null);
switch(orientation) {
case Orientation.portrait:
height = _kTimePickerHeaderPortraitHeight;
padding = const EdgeInsets.symmetric(horizontal: 24.0);
break; break;
case Orientation.landscape: case Brightness.dark:
width = _kTimePickerHeaderLandscapeWidth; activeColor = Colors.white;
padding = const EdgeInsets.symmetric(horizontal: 16.0); inactiveColor = Colors.white70;
break; break;
} }
final TextTheme headerTextTheme = themeData.primaryTextTheme;
final TextStyle baseHeaderStyle = _getBaseHeaderStyle(headerTextTheme);
final _TimePickerFragmentContext fragmentContext = new _TimePickerFragmentContext(
headerTextTheme: headerTextTheme,
textDirection: Directionality.of(context),
selectedTime: selectedTime,
mode: mode,
activeColor: activeColor,
activeStyle: baseHeaderStyle.copyWith(color: activeColor),
inactiveColor: inactiveColor,
inactiveStyle: baseHeaderStyle.copyWith(color: inactiveColor),
onTimeChange: onChanged,
onModeChange: _handleChangeMode,
);
final _TimePickerHeaderFormat format = _buildHeaderFormat(timeOfDayFormat, fragmentContext);
return new Container( return new Container(
width: width, width: width,
height: height, height: height,
padding: padding, padding: padding,
color: backgroundColor, color: backgroundColor,
child: new CustomMultiChildLayout( child: new CustomMultiChildLayout(
delegate: new _TimePickerHeaderLayout(orientation), delegate: new _TimePickerHeaderLayout(orientation, format),
children: <Widget>[ children: format.pieces
new LayoutId(id: _TimePickerHeaderId.hour, child: hour), .expand<_TimePickerHeaderFragment>((_TimePickerHeaderPiece piece) => piece.fragments)
new LayoutId(id: _TimePickerHeaderId.colon, child: colon), .map<Widget>((_TimePickerHeaderFragment fragment) {
new LayoutId(id: _TimePickerHeaderId.minute, child: minute), return new LayoutId(
new LayoutId(id: _TimePickerHeaderId.period, child: dayPeriodPicker), id: fragment.layoutId,
], child: fragment.widget,
);
})
.toList(),
) )
); );
} }
...@@ -364,10 +796,25 @@ List<TextPainter> _initPainters(TextTheme textTheme, List<String> labels) { ...@@ -364,10 +796,25 @@ List<TextPainter> _initPainters(TextTheme textTheme, List<String> labels) {
return painters; return painters;
} }
List<TextPainter> _initHours(TextTheme textTheme) { enum _DialRing {
return _initPainters(textTheme, <String>[ outer,
inner,
}
List<TextPainter> _initHours(TextTheme textTheme, _DialRing ring, bool is24h) {
const List<String> amHours = const <String>[
'12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11' '12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'
]); ];
const List<String> pmHours = const <String>[
'00', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'
];
switch (ring) {
case _DialRing.outer:
return _initPainters(textTheme, is24h ? pmHours : amHours);
case _DialRing.inner:
return is24h ? _initPainters(textTheme, amHours) : null;
}
return null;
} }
List<TextPainter> _initMinutes(TextTheme textTheme) { List<TextPainter> _initMinutes(TextTheme textTheme) {
...@@ -378,18 +825,24 @@ List<TextPainter> _initMinutes(TextTheme textTheme) { ...@@ -378,18 +825,24 @@ List<TextPainter> _initMinutes(TextTheme textTheme) {
class _DialPainter extends CustomPainter { class _DialPainter extends CustomPainter {
const _DialPainter({ const _DialPainter({
this.primaryLabels, @required this.primaryOuterLabels,
this.secondaryLabels, @required this.primaryInnerLabels,
this.backgroundColor, @required this.secondaryOuterLabels,
this.accentColor, @required this.secondaryInnerLabels,
this.theta @required this.backgroundColor,
@required this.accentColor,
@required this.theta,
@required this.activeRing,
}); });
final List<TextPainter> primaryLabels; final List<TextPainter> primaryOuterLabels;
final List<TextPainter> secondaryLabels; final List<TextPainter> primaryInnerLabels;
final List<TextPainter> secondaryOuterLabels;
final List<TextPainter> secondaryInnerLabels;
final Color backgroundColor; final Color backgroundColor;
final Color accentColor; final Color accentColor;
final double theta; final double theta;
final _DialRing activeRing;
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
...@@ -399,28 +852,41 @@ class _DialPainter extends CustomPainter { ...@@ -399,28 +852,41 @@ class _DialPainter extends CustomPainter {
canvas.drawCircle(centerPoint, radius, new Paint()..color = backgroundColor); canvas.drawCircle(centerPoint, radius, new Paint()..color = backgroundColor);
const double labelPadding = 24.0; const double labelPadding = 24.0;
final double labelRadius = radius - labelPadding; final double outerLabelRadius = radius - labelPadding;
Offset getOffsetForTheta(double theta) { final double innerLabelRadius = radius - labelPadding * 2.5;
Offset getOffsetForTheta(double theta, _DialRing ring) {
double labelRadius;
switch (ring) {
case _DialRing.outer:
labelRadius = outerLabelRadius;
break;
case _DialRing.inner:
labelRadius = innerLabelRadius;
break;
}
return center + new Offset(labelRadius * math.cos(theta), return center + new Offset(labelRadius * math.cos(theta),
-labelRadius * math.sin(theta)); -labelRadius * math.sin(theta));
} }
void paintLabels(List<TextPainter> labels) { void paintLabels(List<TextPainter> labels, _DialRing ring) {
if (labels == null)
return;
final double labelThetaIncrement = -_kTwoPi / labels.length; final double labelThetaIncrement = -_kTwoPi / labels.length;
double labelTheta = math.PI / 2.0; double labelTheta = math.PI / 2.0;
for (TextPainter label in labels) { for (TextPainter label in labels) {
final Offset labelOffset = new Offset(-label.width / 2.0, -label.height / 2.0); final Offset labelOffset = new Offset(-label.width / 2.0, -label.height / 2.0);
label.paint(canvas, getOffsetForTheta(labelTheta) + labelOffset); label.paint(canvas, getOffsetForTheta(labelTheta, ring) + labelOffset);
labelTheta += labelThetaIncrement; labelTheta += labelThetaIncrement;
} }
} }
paintLabels(primaryLabels); paintLabels(primaryOuterLabels, _DialRing.outer);
paintLabels(primaryInnerLabels, _DialRing.inner);
final Paint selectorPaint = new Paint() final Paint selectorPaint = new Paint()
..color = accentColor; ..color = accentColor;
final Offset focusedPoint = getOffsetForTheta(theta); final Offset focusedPoint = getOffsetForTheta(theta, activeRing);
final double focusedRadius = labelPadding - 4.0; final double focusedRadius = labelPadding - 4.0;
canvas.drawCircle(centerPoint, 4.0, selectorPaint); canvas.drawCircle(centerPoint, 4.0, selectorPaint);
canvas.drawCircle(focusedPoint, focusedRadius, selectorPaint); canvas.drawCircle(focusedPoint, focusedRadius, selectorPaint);
...@@ -433,17 +899,21 @@ class _DialPainter extends CustomPainter { ...@@ -433,17 +899,21 @@ class _DialPainter extends CustomPainter {
canvas canvas
..save() ..save()
..clipPath(new Path()..addOval(focusedRect)); ..clipPath(new Path()..addOval(focusedRect));
paintLabels(secondaryLabels); paintLabels(secondaryOuterLabels, _DialRing.outer);
paintLabels(secondaryInnerLabels, _DialRing.inner);
canvas.restore(); canvas.restore();
} }
@override @override
bool shouldRepaint(_DialPainter oldPainter) { bool shouldRepaint(_DialPainter oldPainter) {
return oldPainter.primaryLabels != primaryLabels return oldPainter.primaryOuterLabels != primaryOuterLabels
|| oldPainter.secondaryLabels != secondaryLabels || oldPainter.primaryInnerLabels != primaryInnerLabels
|| oldPainter.secondaryOuterLabels != secondaryOuterLabels
|| oldPainter.secondaryInnerLabels != secondaryInnerLabels
|| oldPainter.backgroundColor != backgroundColor || oldPainter.backgroundColor != backgroundColor
|| oldPainter.accentColor != accentColor || oldPainter.accentColor != accentColor
|| oldPainter.theta != theta; || oldPainter.theta != theta
|| oldPainter.activeRing != activeRing;
} }
} }
...@@ -451,11 +921,13 @@ class _Dial extends StatefulWidget { ...@@ -451,11 +921,13 @@ class _Dial extends StatefulWidget {
const _Dial({ const _Dial({
@required this.selectedTime, @required this.selectedTime,
@required this.mode, @required this.mode,
@required this.is24h,
@required this.onChanged @required this.onChanged
}) : assert(selectedTime != null); }) : assert(selectedTime != null);
final TimeOfDay selectedTime; final TimeOfDay selectedTime;
final _TimePickerMode mode; final _TimePickerMode mode;
final bool is24h;
final ValueChanged<TimeOfDay> onChanged; final ValueChanged<TimeOfDay> onChanged;
@override @override
...@@ -480,8 +952,15 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { ...@@ -480,8 +952,15 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
@override @override
void didUpdateWidget(_Dial oldWidget) { void didUpdateWidget(_Dial oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.mode != oldWidget.mode && !_dragging) if (widget.mode != oldWidget.mode) {
_animateTo(_getThetaForTime(widget.selectedTime)); if (!_dragging)
_animateTo(_getThetaForTime(widget.selectedTime));
}
if (widget.mode == _TimePickerMode.hour && widget.is24h && widget.selectedTime.period == DayPeriod.am) {
_activeRing = _DialRing.inner;
} else {
_activeRing = _DialRing.outer;
}
} }
@override @override
...@@ -521,10 +1000,18 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { ...@@ -521,10 +1000,18 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
TimeOfDay _getTimeForTheta(double theta) { TimeOfDay _getTimeForTheta(double theta) {
final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0; final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0;
if (widget.mode == _TimePickerMode.hour) { if (widget.mode == _TimePickerMode.hour) {
final int hourOfPeriod = (fraction * _kHoursPerPeriod).round() % _kHoursPerPeriod; int newHour = (fraction * _kHoursPerPeriod).round() % _kHoursPerPeriod;
return widget.selectedTime.replacing( if (widget.is24h) {
hour: hourOfPeriod + widget.selectedTime.periodOffset if (_activeRing == _DialRing.outer) {
); if (newHour != 0)
newHour = (newHour + _kHoursPerPeriod) % _kHoursPerDay;
} else if (newHour == 0) {
newHour = _kHoursPerPeriod;
}
} else {
newHour = newHour + widget.selectedTime.periodOffset;
}
return widget.selectedTime.replacing(hour: newHour);
} else { } else {
return widget.selectedTime.replacing( return widget.selectedTime.replacing(
minute: (fraction * _kMinutesPerHour).round() % _kMinutesPerHour minute: (fraction * _kMinutesPerHour).round() % _kMinutesPerHour
...@@ -547,11 +1034,20 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { ...@@ -547,11 +1034,20 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
_thetaTween _thetaTween
..begin = angle ..begin = angle
..end = angle; // The controller doesn't animate during the pan gesture. ..end = angle; // The controller doesn't animate during the pan gesture.
final RenderBox box = context.findRenderObject();
final double radius = box.size.shortestSide / 2.0;
if (widget.mode == _TimePickerMode.hour && widget.is24h) {
if (offset.distance * 1.5 < radius)
_activeRing = _DialRing.inner;
else
_activeRing = _DialRing.outer;
}
}); });
} }
Offset _position; Offset _position;
Offset _center; Offset _center;
_DialRing _activeRing = _DialRing.outer;
void _handlePanStart(DragStartDetails details) { void _handlePanStart(DragStartDetails details) {
assert(!_dragging); assert(!_dragging);
...@@ -592,16 +1088,22 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { ...@@ -592,16 +1088,22 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
} }
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
List<TextPainter> primaryLabels; List<TextPainter> primaryOuterLabels;
List<TextPainter> secondaryLabels; List<TextPainter> primaryInnerLabels;
List<TextPainter> secondaryOuterLabels;
List<TextPainter> secondaryInnerLabels;
switch (widget.mode) { switch (widget.mode) {
case _TimePickerMode.hour: case _TimePickerMode.hour:
primaryLabels = _initHours(theme.textTheme); primaryOuterLabels = _initHours(theme.textTheme, _DialRing.outer, widget.is24h);
secondaryLabels = _initHours(theme.accentTextTheme); secondaryOuterLabels = _initHours(theme.accentTextTheme, _DialRing.outer, widget.is24h);
primaryInnerLabels = _initHours(theme.textTheme, _DialRing.inner, widget.is24h);
secondaryInnerLabels = _initHours(theme.accentTextTheme, _DialRing.inner, widget.is24h);
break; break;
case _TimePickerMode.minute: case _TimePickerMode.minute:
primaryLabels = _initMinutes(theme.textTheme); primaryOuterLabels = _initMinutes(theme.textTheme);
secondaryLabels = _initMinutes(theme.accentTextTheme); primaryInnerLabels = null;
secondaryOuterLabels = _initMinutes(theme.accentTextTheme);
secondaryInnerLabels = null;
break; break;
} }
...@@ -612,11 +1114,14 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin { ...@@ -612,11 +1114,14 @@ class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
child: new CustomPaint( child: new CustomPaint(
key: const ValueKey<String>('time-picker-dial'), // used for testing. key: const ValueKey<String>('time-picker-dial'), // used for testing.
painter: new _DialPainter( painter: new _DialPainter(
primaryLabels: primaryLabels, primaryOuterLabels: primaryOuterLabels,
secondaryLabels: secondaryLabels, primaryInnerLabels: primaryInnerLabels,
secondaryOuterLabels: secondaryOuterLabels,
secondaryInnerLabels: secondaryInnerLabels,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
accentColor: themeData.accentColor, accentColor: themeData.accentColor,
theta: _theta.value theta: _theta.value,
activeRing: _activeRing,
) )
) )
); );
...@@ -686,12 +1191,15 @@ class _TimePickerDialogState extends State<_TimePickerDialog> { ...@@ -686,12 +1191,15 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat;
final Widget picker = new Padding( final Widget picker = new Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: new AspectRatio( child: new AspectRatio(
aspectRatio: 1.0, aspectRatio: 1.0,
child: new _Dial( child: new _Dial(
mode: _mode, mode: _mode,
is24h: _getHourFormat(timeOfDayFormat) != _TimePickerHourFormat.h,
selectedTime: _selectedTime, selectedTime: _selectedTime,
onChanged: _handleTimeChanged, onChanged: _handleTimeChanged,
) )
......
...@@ -43,7 +43,7 @@ AxisDirection _getDefaultCrossAxisDirection(BuildContext context, AxisDirection ...@@ -43,7 +43,7 @@ AxisDirection _getDefaultCrossAxisDirection(BuildContext context, AxisDirection
/// example, if the [axisDirection] is [AxisDirection.down], the first sliver /// example, if the [axisDirection] is [AxisDirection.down], the first sliver
/// before [center] is placed above the [center]. The slivers that are later in /// before [center] is placed above the [center]. The slivers that are later in
/// the child list than [center] are placed in order in the [axisDirection]. For /// the child list than [center] are placed in order in the [axisDirection]. For
/// example, in the preceeding scenario, the first sliver after [center] is /// example, in the preceding scenario, the first sliver after [center] is
/// placed below the [center]. /// placed below the [center].
/// ///
/// [Viewport] cannot contain box children directly. Instead, use a /// [Viewport] cannot contain box children directly. Instead, use a
......
...@@ -2,19 +2,24 @@ ...@@ -2,19 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'feedback_tester.dart'; import 'feedback_tester.dart';
class _TimePickerLauncher extends StatelessWidget { class _TimePickerLauncher extends StatelessWidget {
const _TimePickerLauncher({ Key key, this.onChanged }) : super(key: key); const _TimePickerLauncher({ Key key, this.onChanged, this.locale }) : super(key: key);
final ValueChanged<TimeOfDay> onChanged; final ValueChanged<TimeOfDay> onChanged;
final Locale locale;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new MaterialApp( return new MaterialApp(
locale: locale,
home: new Material( home: new Material(
child: new Center( child: new Center(
child: new Builder( child: new Builder(
...@@ -36,15 +41,18 @@ class _TimePickerLauncher extends StatelessWidget { ...@@ -36,15 +41,18 @@ class _TimePickerLauncher extends StatelessWidget {
} }
} }
Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged) async { Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged,
await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged)); { Locale locale: const Locale('en', 'US') }) async {
await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged, locale: locale,));
await tester.tap(find.text('X')); await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1)); await tester.pumpAndSettle(const Duration(seconds: 1));
return tester.getCenter(find.byKey(const Key('time-picker-dial'))); return tester.getCenter(find.byKey(const Key('time-picker-dial')));
} }
Future<Null> finishPicker(WidgetTester tester) async { Future<Null> finishPicker(WidgetTester tester) async {
await tester.tap(find.text('OK')); final Element timePickerElement = tester.element(find.byElementPredicate((Element element) => element.widget.runtimeType.toString() == '_TimePickerDialog'));
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(timePickerElement);
await tester.tap(find.text(materialLocalizations.okButtonLabel));
await tester.pumpAndSettle(const Duration(seconds: 1)); await tester.pumpAndSettle(const Duration(seconds: 1));
} }
...@@ -197,4 +205,78 @@ void main() { ...@@ -197,4 +205,78 @@ void main() {
expect(feedback.hapticCount, 3); expect(feedback.hapticCount, 3);
}); });
}); });
group('localization', () {
testWidgets('can localize the header in all known formats', (WidgetTester tester) async {
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final Map<Locale, List<String>> locales = <Locale, List<String>>{
const Locale('en', 'US'): const <String>['hour h', 'string :', 'minute', 'period'], //'h:mm a'
const Locale('en', 'GB'): const <String>['hour HH', 'string :', 'minute'], //'HH:mm'
const Locale('es', 'ES'): const <String>['hour H', 'string :', 'minute'], //'H:mm'
const Locale('fr', 'CA'): const <String>['hour HH', 'string h', 'minute'], //'HH \'h\' mm'
const Locale('zh', 'ZH'): const <String>['period', 'hour h', 'string :', 'minute'], //'ah:mm'
};
for (Locale locale in locales.keys) {
final Offset center = await startPicker(tester, (TimeOfDay time) { }, locale: locale);
final List<String> actual = <String>[];
tester.element(find.byType(CustomMultiChildLayout)).visitChildren((Element child) {
final LayoutId layout = child.widget;
final String fragmentType = '${layout.child.runtimeType}';
final dynamic widget = layout.child;
if (fragmentType == '_MinuteControl') {
actual.add('minute');
} else if (fragmentType == '_DayPeriodControl') {
actual.add('period');
} else if (fragmentType == '_HourControl') {
actual.add('hour ${widget.hourFormat.toString().split('.').last}');
} else if (fragmentType == '_StringFragment') {
actual.add('string ${widget.value}');
} else {
fail('Unsupported fragment type: $fragmentType');
}
});
expect(actual, locales[locale]);
await tester.tapAt(new Offset(center.dx, center.dy - 50.0));
await finishPicker(tester);
}
});
testWidgets('uses single-ring 12-hour dial for h hour format', (WidgetTester tester) async {
// Tap along the segment stretching from the center to the edge at
// 12:00 AM position. Because there's only one ring, no matter where you
// tap the time will be the same. See the 24-hour dial test that behaves
// differently.
for (int i = 1; i < 10; i++) {
TimeOfDay result;
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
final double dy = (size.height / 2.0 / 10) * i;
await tester.tapAt(new Offset(center.dx, center.dy - dy));
await finishPicker(tester);
expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
}
});
testWidgets('uses two-ring 24-hour dial for H and HH hour formats', (WidgetTester tester) async {
const List<Locale> locales = const <Locale>[
const Locale('en', 'GB'), // HH
const Locale('es', 'ES'), // H
];
for (Locale locale in locales) {
// Tap along the segment stretching from the center to the edge at
// 12:00 AM position. There are two rings. At ~70% mark, the ring
// switches between inner ring and outer ring.
for (int i = 1; i < 10; i++) {
TimeOfDay result;
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; }, locale: locale);
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
final double dy = (size.height / 2.0 / 10) * i;
await tester.tapAt(new Offset(center.dx, center.dy - dy));
await finishPicker(tester);
expect(result, equals(new TimeOfDay(hour: i < 7 ? 12 : 0, minute: 0)));
}
}
});
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment