diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 44cf8fd6d..30118f540 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -505,6 +505,17 @@ "@aboutCreditsWorldAtlas2": {}, "aboutCreditsTranslators": "Translators:", "@aboutCreditsTranslators": {}, + "aboutCreditsTranslatorLine": "{language}: {names}", + "@aboutCreditsTranslatorLine": { + "placeholders": { + "language": { + "type": "String" + }, + "names": { + "type": "String" + } + } + }, "aboutLicenses": "Open-Source Licenses", "@aboutLicenses": {}, @@ -893,8 +904,11 @@ "settingsSaveSearchHistory": "Save search history", "@settingsSaveSearchHistory": {}, - "settingsHiddenFiltersTile": "Hidden filters", - "@settingsHiddenFiltersTile": {}, + "settingsHiddenItemsTile": "Hidden items", + "@settingsHiddenItemsTile": {}, + "settingsHiddenItemsTitle": "Hidden Items", + "@settingsHiddenItemsTitle": {}, + "settingsHiddenFiltersTitle": "Hidden Filters", "@settingsHiddenFiltersTitle": {}, "settingsHiddenFiltersBanner": "Photos and videos matching hidden filters will not appear in your collection.", @@ -902,14 +916,10 @@ "settingsHiddenFiltersEmpty": "No hidden filters", "@settingsHiddenFiltersEmpty": {}, - "settingsHiddenPathsTile": "Hidden paths", - "@settingsHiddenPathsTile": {}, "settingsHiddenPathsTitle": "Hidden Paths", "@settingsHiddenPathsTitle": {}, "settingsHiddenPathsBanner": "Photos and videos in these folders, or any of their subfolders, will not appear in your collection.", "@settingsHiddenPathsBanner": {}, - "settingsHiddenPathsEmpty": "No hidden paths", - "@settingsHiddenPathsEmpty": {}, "addPathTooltip": "Add path", "@addPathTooltip": {}, diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 957d57c04..80fdbc987 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -24,7 +24,7 @@ "removeTooltip": "Supprimer", "resetButtonTooltip": "Réinitialiser", - "doubleBackExitMessage": "Presser «\u00A0retour\u00A0» à nouveau pour quitter.", + "doubleBackExitMessage": "Pressez «\u00A0retour\u00A0» à nouveau pour quitter.", "sourceStateLoading": "Chargement", "sourceStateCataloguing": "Classification", @@ -233,7 +233,8 @@ "aboutCredits": "Remerciements", "aboutCreditsWorldAtlas1": "Cette app utilise un fichier TopoJSON de ", "aboutCreditsWorldAtlas2": "sous licence ISC.", - "aboutCreditsTranslators": "Traducteurs:", + "aboutCreditsTranslators": "Traducteurs :", + "aboutCreditsTranslatorLine": "{language} : {names}", "aboutLicenses": "Licences open-source", "aboutLicensesBanner": "Cette app utilise ces librairies et packages open-source.", @@ -426,15 +427,15 @@ "settingsAllowErrorReporting": "Autoriser l’envoi de rapports d’erreur", "settingsSaveSearchHistory": "Maintenir un historique des recherches", - "settingsHiddenFiltersTile": "Filtres masqués", + "settingsHiddenItemsTile": "Éléments masqués", + "settingsHiddenItemsTitle": "Éléments masqués", + "settingsHiddenFiltersTitle": "Filtres masqués", "settingsHiddenFiltersBanner": "Les images et vidéos correspondantes aux filtres masqués n’apparaîtront pas dans votre collection.", "settingsHiddenFiltersEmpty": "Aucun filtre masqué", - "settingsHiddenPathsTile": "Chemins masqués", "settingsHiddenPathsTitle": "Chemins masqués", "settingsHiddenPathsBanner": "Les images et vidéos dans ces dossiers, ou leurs sous-dossiers, n’apparaîtront pas dans votre collection.", - "settingsHiddenPathsEmpty": "Aucun chemin masqué", "addPathTooltip": "Ajouter un chemin", "settingsStorageAccessTile": "Accès au stockage", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 060cb2ad6..cea923f45 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -233,6 +233,7 @@ "aboutCreditsWorldAtlas1": "이 앱은", "aboutCreditsWorldAtlas2": "의 TopoJSON 파일(ISC 라이선스)을 이용합니다.", "aboutCreditsTranslators": "번역가:", + "aboutCreditsTranslatorLine": "{language}: {names}", "aboutLicenses": "오픈 소스 라이선스", "aboutLicensesBanner": "이 앱은 다음의 오픈 소스 패키지와 라이브러리를 이용합니다.", @@ -425,15 +426,15 @@ "settingsAllowErrorReporting": "오류 보고서 보내기", "settingsSaveSearchHistory": "검색기록", - "settingsHiddenFiltersTile": "숨겨진 필터", + "settingsHiddenItemsTile": "숨겨진 항목", + "settingsHiddenItemsTitle": "숨겨진 항목", + "settingsHiddenFiltersTitle": "숨겨진 필터", "settingsHiddenFiltersBanner": "이 필터에 맞는 사진과 동영상이 숨겨지고 있으며 이 앱에서 보여지지 않을 것입니다.", "settingsHiddenFiltersEmpty": "숨겨진 필터가 없습니다", - "settingsHiddenPathsTile": "숨겨진 경로", "settingsHiddenPathsTitle": "숨겨진 경로", "settingsHiddenPathsBanner": "이 경로에 있는 사진과 동영상이 숨겨지고 있으며 이 앱에서 보여지지 않을 것입니다.", - "settingsHiddenPathsEmpty": "숨겨진 경로가 없습니다", "addPathTooltip": "경로 추가", "settingsStorageAccessTile": "저장공간 접근", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 757d0ffbc..0c7bd51c4 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -232,6 +232,7 @@ "aboutCreditsWorldAtlas1": "Это приложение использует файл TopoJSON из", "aboutCreditsWorldAtlas2": "под лицензией ISC.", "aboutCreditsTranslators": "Переводчики:", + "aboutCreditsTranslatorLine": "{language}: {names}", "aboutLicenses": "Лицензии с открытым исходным кодом", "aboutLicensesBanner": "Это приложение использует следующие пакеты и библиотеки с открытым исходным кодом.", @@ -423,15 +424,12 @@ "settingsAllowErrorReporting": "Разрешить анонимную отправку логов", "settingsSaveSearchHistory": "Сохранять историю поиска", - "settingsHiddenFiltersTile": "Скрытые фильтры", "settingsHiddenFiltersTitle": "Скрытые фильтры", "settingsHiddenFiltersBanner": "Фотографии и видео, соответствующие скрытым фильтрам, не появятся в вашей коллекции.", "settingsHiddenFiltersEmpty": "Нет скрытых фильтров", - "settingsHiddenPathsTile": "Скрытые каталоги", "settingsHiddenPathsTitle": "Скрытые каталоги", "settingsHiddenPathsBanner": "Фотографии и видео в этих каталогах или любых их вложенных каталогах не будут отображаться в вашей коллекции.", - "settingsHiddenPathsEmpty": "Нет скрытых каталогов", "addPathTooltip": "Добавить каталог", "settingsStorageAccessTile": "Доступ к хранилищу", diff --git a/lib/widgets/about/credits.dart b/lib/widgets/about/credits.dart index 3bc167ca4..4364adb92 100644 --- a/lib/widgets/about/credits.dart +++ b/lib/widgets/about/credits.dart @@ -8,12 +8,13 @@ import 'package:flutter/material.dart'; class AboutCredits extends StatelessWidget { const AboutCredits({Key? key}) : super(key: key); - static const translations = [ - 'Русский: D3ZOXY', - ]; + static const translators = { + 'Русский': 'D3ZOXY', + }; @override Widget build(BuildContext context) { + final l10n = context.l10n; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( @@ -23,13 +24,13 @@ class AboutCredits extends StatelessWidget { constraints: const BoxConstraints(minHeight: 48), child: Align( alignment: AlignmentDirectional.centerStart, - child: Text(context.l10n.aboutCredits, style: Constants.titleTextStyle), + child: Text(l10n.aboutCredits, style: Constants.titleTextStyle), ), ), Text.rich( TextSpan( children: [ - TextSpan(text: context.l10n.aboutCreditsWorldAtlas1), + TextSpan(text: l10n.aboutCreditsWorldAtlas1), const WidgetSpan( child: LinkChip( text: 'World Atlas', @@ -38,17 +39,19 @@ class AboutCredits extends StatelessWidget { ), alignment: PlaceholderAlignment.middle, ), - TextSpan(text: context.l10n.aboutCreditsWorldAtlas2), + TextSpan(text: l10n.aboutCreditsWorldAtlas2), ], ), ), const SizedBox(height: 16), - Text(context.l10n.aboutCreditsTranslators), - ...translations.map( - (line) => Padding( - padding: const EdgeInsetsDirectional.only(start: 8, top: 8), - child: Text(line), - ), + Text(l10n.aboutCreditsTranslators), + ...translators.entries.map( + (kv) { + return Padding( + padding: const EdgeInsetsDirectional.only(start: 8, top: 8), + child: Text(l10n.aboutCreditsTranslatorLine(kv.key, kv.value)), + ); + }, ), const SizedBox(height: 16), ], diff --git a/lib/widgets/dialogs/entry_editors/edit_entry_tags_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_entry_tags_dialog.dart index 0779ad10e..cebab3027 100644 --- a/lib/widgets/dialogs/entry_editors/edit_entry_tags_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_entry_tags_dialog.dart @@ -154,7 +154,7 @@ class _TagEditorPageState extends State { duration: Durations.tagEditorTransition, ), ), - const Divider(height: 1), + const Divider(height: 0), _FilterRow( title: l10n.tagEditorSectionRecent, filters: recentFilters, diff --git a/lib/widgets/settings/common/quick_actions/editor_page.dart b/lib/widgets/settings/common/quick_actions/editor_page.dart index 0139ded0d..1b1dc7f74 100644 --- a/lib/widgets/settings/common/quick_actions/editor_page.dart +++ b/lib/widgets/settings/common/quick_actions/editor_page.dart @@ -145,7 +145,7 @@ class _QuickActionEditorBodyState extends State extends State const HiddenFilterPage(), - ), - ); - }, - ); - } -} - -class HiddenFilterPage extends StatelessWidget { - static const routeName = '/settings/hidden_filters'; - - const HiddenFilterPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(context.l10n.settingsHiddenFiltersTitle), - ), - body: SafeArea( - child: Selector>( - selector: (context, s) => settings.hiddenFilters.where((v) => v is! PathFilter).toSet(), - builder: (context, hiddenFilters, child) { - if (hiddenFilters.isEmpty) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const _Header(), - const Divider(), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8), - child: EmptyContent( - icon: AIcons.hide, - text: context.l10n.settingsHiddenFiltersEmpty, - ), - ), - ), - ], - ); - } - - final filterList = hiddenFilters.toList()..sort(); - return ListView( - children: [ - const _Header(), - const Divider(), - Padding( - padding: const EdgeInsets.all(8), - child: Wrap( - spacing: 8, - runSpacing: 8, - children: filterList - .map((filter) => AvesFilterChip( - filter: filter, - removable: true, - onTap: (filter) => context.read().changeFilterVisibility({filter}, true), - onLongPress: null, - )) - .toList(), - ), - ), - ], - ); - }, - ), - ), - ); - } -} - -class _Header extends StatelessWidget { - const _Header({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - child: Row( - children: [ - const Icon(AIcons.info), - const SizedBox(width: 16), - Expanded(child: Text(context.l10n.settingsHiddenFiltersBanner)), - ], - ), - ); - } -} diff --git a/lib/widgets/settings/privacy/hidden_items.dart b/lib/widgets/settings/privacy/hidden_items.dart new file mode 100644 index 000000000..2fb295f99 --- /dev/null +++ b/lib/widgets/settings/privacy/hidden_items.dart @@ -0,0 +1,198 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/path.dart'; +import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/theme/icons.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; +import 'package:aves/widgets/common/identity/buttons.dart'; +import 'package:aves/widgets/common/identity/empty.dart'; +import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; + +class HiddenItemsTile extends StatelessWidget { + const HiddenItemsTile({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(context.l10n.settingsHiddenItemsTile), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: HiddenItemsPage.routeName), + builder: (context) => const HiddenItemsPage(), + ), + ); + }, + ); + } +} + +class HiddenItemsPage extends StatelessWidget { + static const routeName = '/settings/hidden_items'; + + const HiddenItemsPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + final tabs = >[ + Tuple2( + Tab(text: l10n.settingsHiddenFiltersTitle), + const _HiddenFilters(), + ), + Tuple2( + Tab(text: l10n.settingsHiddenPathsTitle), + const _HiddenPaths(), + ), + ]; + + return MediaQueryDataProvider( + child: DefaultTabController( + length: tabs.length, + child: Scaffold( + appBar: AppBar( + title: Text(l10n.settingsHiddenItemsTitle), + bottom: TabBar( + tabs: tabs.map((t) => t.item1).toList(), + ), + ), + body: SafeArea( + child: TabBarView( + children: tabs.map((t) => t.item2).toList(), + ), + ), + ), + ), + ); + } +} + +class _HiddenFilters extends StatelessWidget { + const _HiddenFilters({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Selector>( + selector: (context, s) => settings.hiddenFilters.where((v) => v is! PathFilter).toSet(), + builder: (context, hiddenFilters, child) { + if (hiddenFilters.isEmpty) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _Banner(bannerText: context.l10n.settingsHiddenFiltersBanner), + const Divider(height: 0), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8), + child: EmptyContent( + icon: AIcons.hide, + text: context.l10n.settingsHiddenFiltersEmpty, + ), + ), + ), + ], + ); + } + + final filterList = hiddenFilters.toList()..sort(); + return ListView( + children: [ + _Banner(bannerText: context.l10n.settingsHiddenFiltersBanner), + const Divider(height: 0), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: filterList + .map((filter) => AvesFilterChip( + filter: filter, + removable: true, + onTap: (filter) => context.read().changeFilterVisibility({filter}, true), + onLongPress: null, + )) + .toList(), + ), + ), + ], + ); + }, + ); + } +} + +class _HiddenPaths extends StatelessWidget { + const _HiddenPaths({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Selector>( + selector: (context, s) => settings.hiddenFilters.whereType().toSet(), + builder: (context, hiddenPaths, child) { + final pathList = hiddenPaths.toList()..sort(); + return Column( + children: [ + _Banner(bannerText: context.l10n.settingsHiddenPathsBanner), + const Divider(height: 0), + Flexible( + child: ListView( + shrinkWrap: true, + children: [ + ...pathList.map((pathFilter) => ListTile( + title: Text(pathFilter.path), + dense: true, + trailing: IconButton( + icon: const Icon(AIcons.clear), + onPressed: () { + context.read().changeFilterVisibility({pathFilter}, true); + }, + tooltip: context.l10n.removeTooltip, + ), + )), + ], + ), + ), + const Divider(height: 0), + const SizedBox(height: 8), + AvesOutlinedButton( + icon: const Icon(AIcons.add), + label: context.l10n.addPathTooltip, + onPressed: () async { + final path = await storageService.selectDirectory(); + if (path != null && path.isNotEmpty) { + context.read().changeFilterVisibility({PathFilter(path)}, false); + } + }, + ), + ], + ); + }, + ); + } +} + +class _Banner extends StatelessWidget { + final String bannerText; + + const _Banner({Key? key, required this.bannerText}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + const Icon(AIcons.info), + const SizedBox(width: 16), + Expanded(child: Text(bannerText)), + ], + ), + ); + } +} diff --git a/lib/widgets/settings/privacy/hidden_paths.dart b/lib/widgets/settings/privacy/hidden_paths.dart deleted file mode 100644 index f3bea046f..000000000 --- a/lib/widgets/settings/privacy/hidden_paths.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:aves/model/filters/path.dart'; -import 'package:aves/model/settings/settings.dart'; -import 'package:aves/model/source/collection_source.dart'; -import 'package:aves/services/common/services.dart'; -import 'package:aves/theme/icons.dart'; -import 'package:aves/widgets/common/extensions/build_context.dart'; -import 'package:aves/widgets/common/identity/empty.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class HiddenPathTile extends StatelessWidget { - const HiddenPathTile({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(context.l10n.settingsHiddenPathsTile), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - settings: const RouteSettings(name: HiddenPathPage.routeName), - builder: (context) => const HiddenPathPage(), - ), - ); - }, - ); - } -} - -class HiddenPathPage extends StatelessWidget { - static const routeName = '/settings/hidden_paths'; - - const HiddenPathPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(context.l10n.settingsHiddenPathsTitle), - actions: [ - IconButton( - icon: const Icon(AIcons.add), - onPressed: () async { - final path = await storageService.selectDirectory(); - if (path != null && path.isNotEmpty) { - context.read().changeFilterVisibility({PathFilter(path)}, false); - } - }, - tooltip: context.l10n.addPathTooltip, - ), - ], - ), - body: SafeArea( - child: Selector>( - selector: (context, s) => settings.hiddenFilters.whereType().toSet(), - builder: (context, hiddenPaths, child) { - if (hiddenPaths.isEmpty) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const _Header(), - const Divider(), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8), - child: EmptyContent( - icon: AIcons.hide, - text: context.l10n.settingsHiddenPathsEmpty, - ), - ), - ), - ], - ); - } - - final pathList = hiddenPaths.toList()..sort(); - return ListView( - children: [ - const _Header(), - const Divider(), - ...pathList.map((pathFilter) => ListTile( - title: Text(pathFilter.path), - dense: true, - trailing: IconButton( - icon: const Icon(AIcons.clear), - onPressed: () { - context.read().changeFilterVisibility({pathFilter}, true); - }, - tooltip: context.l10n.removeTooltip, - ), - )), - ], - ); - }, - ), - ), - ); - } -} - -class _Header extends StatelessWidget { - const _Header({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - child: Row( - children: [ - const Icon(AIcons.info), - const SizedBox(width: 16), - Expanded(child: Text(context.l10n.settingsHiddenPathsBanner)), - ], - ), - ); - } -} diff --git a/lib/widgets/settings/privacy/privacy.dart b/lib/widgets/settings/privacy/privacy.dart index 701ef4860..ec570ca87 100644 --- a/lib/widgets/settings/privacy/privacy.dart +++ b/lib/widgets/settings/privacy/privacy.dart @@ -6,8 +6,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/settings/common/tile_leading.dart'; import 'package:aves/widgets/settings/privacy/access_grants.dart'; -import 'package:aves/widgets/settings/privacy/hidden_filters.dart'; -import 'package:aves/widgets/settings/privacy/hidden_paths.dart'; +import 'package:aves/widgets/settings/privacy/hidden_items.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -63,8 +62,7 @@ class PrivacySection extends StatelessWidget { title: Text(context.l10n.settingsSaveSearchHistory), ), ), - const HiddenFilterTile(), - const HiddenPathTile(), + const HiddenItemsTile(), const StorageAccessTile(), ], ); diff --git a/untranslated.json b/untranslated.json index a8ce4fac0..3b31cec0b 100644 --- a/untranslated.json +++ b/untranslated.json @@ -10,6 +10,8 @@ "resetButtonTooltip", "entryInfoActionEditTags", "settingsViewerMaximumBrightness", + "settingsHiddenItemsTile", + "settingsHiddenItemsTitle", "tagEditorPageTitle", "tagEditorPageNewTagFieldLabel", "tagEditorPageAddTagTooltip"