From 573e6df1c71423eeb5cd4ef69d55e1e0da81f326 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 11 Jan 2022 21:53:05 +0900 Subject: [PATCH] driver: screenshot generation WIP --- .gitignore | 3 +- lib/model/filters/filters.dart | 6 +- lib/model/filters/tag.dart | 8 +- lib/ref/mime_types.dart | 9 ++ .../common/identity/aves_filter_chip.dart | 3 +- lib/widgets/debug/android_env.dart | 49 ---------- lib/widgets/debug/app_debug_action.dart | 5 + lib/widgets/debug/app_debug_page.dart | 63 +++++++++++- .../debug/media_store_scan_dialog.dart | 52 ++++++++++ .../entry_editors/edit_entry_date_dialog.dart | 2 +- lib/widgets/drawer/app_drawer.dart | 2 + lib/widgets/settings/language/locale.dart | 17 +--- lib/widgets/settings/language/locales.dart | 12 +++ lib/widgets/stats/stats_page.dart | 8 +- pubspec.yaml | 2 +- .../assets/{ => shaders}/aves_logo.svg | 0 test_driver/assets/{ => shaders}/ipse.jpg | Bin test_driver/common_test.dart | 16 +++ test_driver/constants.dart | 3 - test_driver/driver_screenshots.dart | 21 ++-- test_driver/driver_screenshots_test.dart | 91 ++++++++++-------- test_driver/driver_shaders.dart | 32 ++---- test_driver/driver_shaders_test.dart | 9 +- test_driver/utils/driver_extension.dart | 20 ++++ 24 files changed, 278 insertions(+), 155 deletions(-) delete mode 100644 lib/widgets/debug/android_env.dart create mode 100644 lib/widgets/debug/app_debug_action.dart create mode 100644 lib/widgets/debug/media_store_scan_dialog.dart create mode 100644 lib/widgets/settings/language/locales.dart rename test_driver/assets/{ => shaders}/aves_logo.svg (100%) rename test_driver/assets/{ => shaders}/ipse.jpg (100%) create mode 100644 test_driver/common_test.dart delete mode 100644 test_driver/constants.dart diff --git a/.gitignore b/.gitignore index 942f9f701..8ee0fc58c 100644 --- a/.gitignore +++ b/.gitignore @@ -45,5 +45,6 @@ app.*.map.json /android/app/profile /android/app/release -# temporary output for screenshot generation +# screenshot generation +/test_driver/assets/screenshots/ /screenshots/ diff --git a/lib/model/filters/filters.dart b/lib/model/filters/filters.dart index af637d41b..058715629 100644 --- a/lib/model/filters/filters.dart +++ b/lib/model/filters/filters.dart @@ -32,6 +32,10 @@ abstract class CollectionFilter extends Equatable implements Comparable toMap(); String toJson() => jsonEncode(toMap()); diff --git a/lib/model/filters/tag.dart b/lib/model/filters/tag.dart index 1ec3b2a4d..0c6105ba7 100644 --- a/lib/model/filters/tag.dart +++ b/lib/model/filters/tag.dart @@ -12,23 +12,25 @@ class TagFilter extends CollectionFilter { @override List get props => [tag]; - TagFilter(this.tag) { + TagFilter(this.tag, {bool not = false}) : super(not: not) { if (tag.isEmpty) { - _test = (entry) => entry.tags.isEmpty; + _test = not ? (entry) => entry.tags.isNotEmpty : (entry) => entry.tags.isEmpty; } else { - _test = (entry) => entry.tags.contains(tag); + _test = not ? (entry) => !entry.tags.contains(tag) : (entry) => entry.tags.contains(tag); } } TagFilter.fromMap(Map json) : this( json['tag'], + not: json['not'] ?? false, ); @override Map toMap() => { 'type': type, 'tag': tag, + 'not': not, }; @override diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart index 2b43ebf5e..978668f31 100644 --- a/lib/ref/mime_types.dart +++ b/lib/ref/mime_types.dart @@ -103,4 +103,13 @@ class MimeTypes { return a == b; } } + + static String? forExtension(String extension) { + switch (extension) { + case '.jpg': + return jpeg; + case '.svg': + return svg; + } + } } diff --git a/lib/widgets/common/identity/aves_filter_chip.dart b/lib/widgets/common/identity/aves_filter_chip.dart index 4c790c718..68bf5285f 100644 --- a/lib/widgets/common/identity/aves_filter_chip.dart +++ b/lib/widgets/common/identity/aves_filter_chip.dart @@ -182,8 +182,9 @@ class _AvesFilterChipState extends State { Flexible( child: Text( filter.getLabel(context), - style: const TextStyle( + style: TextStyle( fontSize: AvesFilterChip.fontSize, + decoration: filter.not ? TextDecoration.lineThrough : null, ), softWrap: false, overflow: TextOverflow.fade, diff --git a/lib/widgets/debug/android_env.dart b/lib/widgets/debug/android_env.dart deleted file mode 100644 index 338e9c7a8..000000000 --- a/lib/widgets/debug/android_env.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:collection'; - -import 'package:aves/services/android_debug_service.dart'; -import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; -import 'package:aves/widgets/viewer/info/common.dart'; -import 'package:flutter/material.dart'; - -class DebugAndroidEnvironmentSection extends StatefulWidget { - const DebugAndroidEnvironmentSection({Key? key}) : super(key: key); - - @override - _DebugAndroidEnvironmentSectionState createState() => _DebugAndroidEnvironmentSectionState(); -} - -class _DebugAndroidEnvironmentSectionState extends State with AutomaticKeepAliveClientMixin { - late Future _loader; - - @override - void initState() { - super.initState(); - _loader = AndroidDebugService.getEnv(); - } - - @override - Widget build(BuildContext context) { - super.build(context); - - return AvesExpansionTile( - title: 'Android Environment', - children: [ - Padding( - padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), - child: FutureBuilder( - future: _loader, - builder: (context, snapshot) { - if (snapshot.hasError) return Text(snapshot.error.toString()); - if (snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink(); - final data = SplayTreeMap.of(snapshot.data!.map((k, v) => MapEntry(k.toString(), v?.toString() ?? 'null'))); - return InfoRowGroup(info: data); - }, - ), - ), - ], - ); - } - - @override - bool get wantKeepAlive => true; -} diff --git a/lib/widgets/debug/app_debug_action.dart b/lib/widgets/debug/app_debug_action.dart new file mode 100644 index 000000000..ffb0c8f1c --- /dev/null +++ b/lib/widgets/debug/app_debug_action.dart @@ -0,0 +1,5 @@ +enum AppDebugAction { + prepScreenshotThumbnails, + prepScreenshotStats, + mediaStoreScanDir, +} diff --git a/lib/widgets/debug/app_debug_page.dart b/lib/widgets/debug/app_debug_page.dart index 1b4a3ec08..40c475603 100644 --- a/lib/widgets/debug/app_debug_page.dart +++ b/lib/widgets/debug/app_debug_page.dart @@ -1,14 +1,23 @@ +import 'dart:async'; + import 'package:aves/model/entry.dart'; +import 'package:aves/model/favourites.dart'; +import 'package:aves/model/filters/path.dart'; +import 'package:aves/model/filters/tag.dart'; +import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/services/analysis_service.dart'; +import 'package:aves/theme/durations.dart'; +import 'package:aves/widgets/common/basic/menu.dart'; import 'package:aves/widgets/common/identity/aves_expansion_tile.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/debug/android_apps.dart'; import 'package:aves/widgets/debug/android_codecs.dart'; import 'package:aves/widgets/debug/android_dirs.dart'; -import 'package:aves/widgets/debug/android_env.dart'; +import 'package:aves/widgets/debug/app_debug_action.dart'; import 'package:aves/widgets/debug/cache.dart'; import 'package:aves/widgets/debug/database.dart'; +import 'package:aves/widgets/debug/media_store_scan_dialog.dart'; import 'package:aves/widgets/debug/overlay.dart'; import 'package:aves/widgets/debug/report.dart'; import 'package:aves/widgets/debug/settings.dart'; @@ -40,6 +49,27 @@ class _AppDebugPageState extends State { child: Scaffold( appBar: AppBar( title: const Text('Debug'), + actions: [ + MenuIconTheme( + child: PopupMenuButton( + // key is expected by test driver + key: const Key('appbar-menu-button'), + itemBuilder: (context) => AppDebugAction.values + .map((v) => PopupMenuItem( + // key is expected by test driver + key: Key('menu-${v.name}'), + value: v, + child: MenuRow(text: v.name), + )) + .toList(), + onSelected: (action) async { + // wait for the popup menu to hide before proceeding with the action + await Future.delayed(Durations.popupMenuAnimation * timeDilation); + unawaited(_onActionSelected(action)); + }, + ), + ), + ], ), body: SafeArea( child: ListView( @@ -49,7 +79,6 @@ class _AppDebugPageState extends State { const DebugAndroidAppSection(), const DebugAndroidCodecSection(), const DebugAndroidDirSection(), - const DebugAndroidEnvironmentSection(), const DebugCacheSection(), const DebugAppDatabaseSection(), const DebugErrorReportingSection(), @@ -127,4 +156,34 @@ class _AppDebugPageState extends State { ], ); } + + Future _onActionSelected(AppDebugAction action) async { + switch (action) { + case AppDebugAction.prepScreenshotThumbnails: + final source = context.read(); + source.changeFilterVisibility(settings.hiddenFilters, true); + source.changeFilterVisibility({ + TagFilter('aves-thumbnail', not: true), + }, false); + await favourites.clear(); + await favourites.add(source.visibleEntries); + break; + case AppDebugAction.prepScreenshotStats: + final source = context.read(); + source.changeFilterVisibility(settings.hiddenFilters, true); + source.changeFilterVisibility({ + PathFilter('/storage/emulated/0/Pictures/Dev'), + }, false); + break; + case AppDebugAction.mediaStoreScanDir: + // scan files copied from test assets + // we do it via the app instead of broadcasting via ADB + // because `MEDIA_SCANNER_SCAN_FILE` intent got deprecated in API 29 + await showDialog( + context: context, + builder: (context) => const MediaStoreScanDirDialog(), + ); + break; + } + } } diff --git a/lib/widgets/debug/media_store_scan_dialog.dart b/lib/widgets/debug/media_store_scan_dialog.dart new file mode 100644 index 000000000..056734252 --- /dev/null +++ b/lib/widgets/debug/media_store_scan_dialog.dart @@ -0,0 +1,52 @@ +import 'dart:io'; + +import 'package:aves/ref/mime_types.dart'; +import 'package:aves/services/common/services.dart'; +import 'package:aves/widgets/dialogs/aves_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:path/path.dart' as p; + +class MediaStoreScanDirDialog extends StatefulWidget { + const MediaStoreScanDirDialog({Key? key}) : super(key: key); + + @override + _MediaStoreScanDirDialogState createState() => _MediaStoreScanDirDialogState(); +} + +class _MediaStoreScanDirDialogState extends State { + final TextEditingController _pathController = TextEditingController(); + bool _processing = false; + + @override + void dispose() { + _pathController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AvesDialog( + content: _processing ? const CircularProgressIndicator() : TextField(controller: _pathController), + actions: [ + TextButton( + onPressed: _processing + ? null + : () async { + final dir = _pathController.text; + if (dir.isNotEmpty) { + setState(() => _processing = true); + await Future.forEach(Directory(dir).listSync(), (file) async { + if (file is File) { + final mimeType = MimeTypes.forExtension(p.extension(file.path)); + await mediaStoreService.scanFile(file.path, mimeType!); + } + }); + } + Navigator.pop(context); + }, + child: const Text('Scan'), + ) + ], + ); + } +} diff --git a/lib/widgets/dialogs/entry_editors/edit_entry_date_dialog.dart b/lib/widgets/dialogs/entry_editors/edit_entry_date_dialog.dart index f3ce87745..639b4bca8 100644 --- a/lib/widgets/dialogs/entry_editors/edit_entry_date_dialog.dart +++ b/lib/widgets/dialogs/entry_editors/edit_entry_date_dialog.dart @@ -314,7 +314,7 @@ class _EditEntryDateDialogState extends State { context: context, initialDate: _setDateTime, firstDate: DateTime(0), - lastDate: DateTime.now(), + lastDate: DateTime(2100), confirmText: context.l10n.nextButtonLabel, ); if (_date == null) return; diff --git a/lib/widgets/drawer/app_drawer.dart b/lib/widgets/drawer/app_drawer.dart index b75060b6e..c5064044f 100644 --- a/lib/widgets/drawer/app_drawer.dart +++ b/lib/widgets/drawer/app_drawer.dart @@ -270,6 +270,8 @@ class _AppDrawerState extends State { } Widget get debugTile => PageNavTile( + // key is expected by test driver + key: const Key('drawer-debug'), topLevel: false, routeName: AppDebugPage.routeName, pageBuilder: (_) => const AppDebugPage(), diff --git a/lib/widgets/settings/language/locale.dart b/lib/widgets/settings/language/locale.dart index 7c42dd2af..1279b1b24 100644 --- a/lib/widgets/settings/language/locale.dart +++ b/lib/widgets/settings/language/locale.dart @@ -5,6 +5,7 @@ import 'package:aves/model/settings/settings.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; +import 'package:aves/widgets/settings/language/locales.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -48,21 +49,7 @@ class LocaleTile extends StatelessWidget { String _getLocaleName(Locale locale) { // the package `flutter_localized_locales` has the answer for all locales // but it comes with 3 MB of assets - switch (locale.languageCode) { - case 'de': - return 'Deutsch'; - case 'en': - return 'English'; - case 'es': - return 'Español (México)'; - case 'fr': - return 'Français'; - case 'ko': - return '한국어'; - case 'ru': - return 'Русский'; - } - return locale.toString(); + return SupportedLocales.languagesByLanguageCode[locale.languageCode] ?? locale.toString(); } LinkedHashMap _getLocaleOptions(BuildContext context) { diff --git a/lib/widgets/settings/language/locales.dart b/lib/widgets/settings/language/locales.dart new file mode 100644 index 000000000..cb992c796 --- /dev/null +++ b/lib/widgets/settings/language/locales.dart @@ -0,0 +1,12 @@ +// this class is kept minimal, without import +// so it can be reused in driver tests +class SupportedLocales { + static const languagesByLanguageCode = { + 'de': 'Deutsch', + 'en': 'English', + 'es': 'Español (México)', + 'fr': 'Français', + 'ko': '한국어', + 'ru': 'Русский', + }; +} diff --git a/lib/widgets/stats/stats_page.dart b/lib/widgets/stats/stats_page.dart index 6ece99212..4e1f7a0ef 100644 --- a/lib/widgets/stats/stats_page.dart +++ b/lib/widgets/stats/stats_page.dart @@ -15,6 +15,7 @@ import 'package:aves/utils/color_utils.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/utils/mime_utils.dart'; import 'package:aves/widgets/collection/collection_page.dart'; +import 'package:aves/widgets/common/basic/insets.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/identity/empty.dart'; import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; @@ -138,8 +139,11 @@ class StatsPage extends StatelessWidget { appBar: AppBar( title: Text(context.l10n.statsPageTitle), ), - body: SafeArea( - child: child, + body: GestureAreaProtectorStack( + child: SafeArea( + bottom: false, + child: child, + ), ), ), ); diff --git a/pubspec.yaml b/pubspec.yaml index 969eda812..e406a82ec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -104,7 +104,7 @@ flutter: # language files: # - /lib/l10n/app_{language}.arb # - /android/app/src/main/res/values-{language}/strings.xml -# - edit locale name resolution for language setting +# - edit locale name in /lib/widgets/settings/language/locales.dart # generate `AppLocalizations` # % flutter gen-l10n diff --git a/test_driver/assets/aves_logo.svg b/test_driver/assets/shaders/aves_logo.svg similarity index 100% rename from test_driver/assets/aves_logo.svg rename to test_driver/assets/shaders/aves_logo.svg diff --git a/test_driver/assets/ipse.jpg b/test_driver/assets/shaders/ipse.jpg similarity index 100% rename from test_driver/assets/ipse.jpg rename to test_driver/assets/shaders/ipse.jpg diff --git a/test_driver/common_test.dart b/test_driver/common_test.dart new file mode 100644 index 000000000..17723a98f --- /dev/null +++ b/test_driver/common_test.dart @@ -0,0 +1,16 @@ +/* + This file is imported by driver test files. + It should not import, directly or indirectly, + `dart:ui`, `flutter/widgets.dart', etc. + */ + +const shadersSourcePicturesDir = 'test_driver/assets/shaders/'; +const shadersTargetPicturesDir = '/sdcard/Pictures/Aves Test Driver/'; +const shadersTargetPicturesDirEmulated = '/storage/emulated/0/Pictures/Aves Test Driver'; + +// Cover items should be: +// - dated in the future, +// - geotagged for each country to cover. +const coversSourcePicturesDir = 'test_driver/assets/screenshots/covers'; +const coversTargetPicturesDir = '/sdcard/Pictures/TD/Aves/'; +const coversTargetPicturesDirEmulated = '/storage/emulated/0/Pictures/TD/Aves'; diff --git a/test_driver/constants.dart b/test_driver/constants.dart deleted file mode 100644 index 7454317a9..000000000 --- a/test_driver/constants.dart +++ /dev/null @@ -1,3 +0,0 @@ -const sourcePicturesDir = 'test_driver/assets/'; -const targetPicturesDir = '/sdcard/Pictures/Aves Test Driver/'; -const targetPicturesDirEmulated = '/storage/emulated/0/Pictures/Aves Test Driver'; diff --git a/test_driver/driver_screenshots.dart b/test_driver/driver_screenshots.dart index af963e965..29eacaeb6 100644 --- a/test_driver/driver_screenshots.dart +++ b/test_driver/driver_screenshots.dart @@ -1,4 +1,5 @@ import 'package:aves/main_play.dart' as app; +import 'package:aves/model/settings/defaults.dart'; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/enums.dart'; @@ -6,15 +7,10 @@ import 'package:aves/widgets/filter_grids/countries_page.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; -void main() { - enableFlutterDriverExtension(); - - // something like `configure().then((_) => app.main());` does not behave as expected - // and starts the app without waiting for `configure` to complete - configureAndLaunch(); -} +void main() => configureAndLaunch(); Future configureAndLaunch() async { + enableFlutterDriverExtension(); await settings.init(monitorPlatformSettings: false); settings // app @@ -25,7 +21,15 @@ Future configureAndLaunch() async { ..homePage = HomePageSetting.collection ..setTileExtent(CountryListPage.routeName, 112) ..setTileLayout(CountryListPage.routeName, TileLayout.grid) + // collection + ..collectionSectionFactor = EntryGroupFactor.month + ..collectionSortFactor = EntrySortFactor.date + ..collectionBrowsingQuickActions = SettingsDefaults.collectionBrowsingQuickActions + ..showThumbnailFavourite = false + ..showThumbnailLocation = false + ..hiddenFilters = {} // viewer + ..viewerQuickActions = SettingsDefaults.viewerQuickActions ..showOverlayOnOpening = true ..showOverlayMinimap = false ..showOverlayInfo = true @@ -37,8 +41,5 @@ Future configureAndLaunch() async { ..infoMapZoom = 11 ..coordinateFormat = CoordinateFormat.dms ..unitSystem = UnitSystem.metric; - - // TODO TLAD covers.set(LocationFilter(LocationLevel.country, location), contentId) - app.main(); } diff --git a/test_driver/driver_screenshots_test.dart b/test_driver/driver_screenshots_test.dart index a9353868e..135693c99 100644 --- a/test_driver/driver_screenshots_test.dart +++ b/test_driver/driver_screenshots_test.dart @@ -2,19 +2,26 @@ import 'dart:async'; import 'dart:io'; +import 'package:aves/widgets/debug/app_debug_action.dart'; +import 'package:aves/widgets/settings/language/locales.dart'; import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; +import 'common_test.dart'; import 'utils/adb_utils.dart'; import 'utils/driver_extension.dart'; late FlutterDriver driver; +String _languageCode = ''; + +const outputDirectory = 'screenshots'; void main() { group('[Aves app]', () { setUpAll(() async { - await Directory(directory).create(); + await Directory(outputDirectory).create(); + await copyContent(coversSourcePicturesDir, coversTargetPicturesDir); await Future.forEach( [ 'deckers.thibault.aves.debug', @@ -28,16 +35,16 @@ void main() { }); tearDownAll(() async { + await removeDirectory(coversTargetPicturesDir); unawaited(driver.close()); }); - [ - 'de', - 'en', - // TODO TLAD other locales - ].forEach((v) async { - setLanguage(v); + test('scan media dir', () => driver.scanMediaDir(coversTargetPicturesDirEmulated)); + SupportedLocales.languagesByLanguageCode.keys.forEach((languageCode) { + setLanguage(languageCode); + configureCollectionVisibility(AppDebugAction.prepScreenshotThumbnails); collection(); + configureCollectionVisibility(AppDebugAction.prepScreenshotStats); viewer(); info(); stats(); @@ -46,24 +53,44 @@ void main() { }, timeout: const Timeout(Duration(seconds: 30))); } -const directory = 'screenshots'; -String screenshotLocale = ''; +Future _search(String query, String chipKey) async { + await driver.tapKeyAndWait('menu-searchCollection'); + await driver.tap(find.byType('TextField')); + await driver.enterText(query); + final chip = find.byValueKey(chipKey); + await driver.waitFor(chip); + await driver.tap(chip); + await driver.waitUntilNoTransientCallbacks(); +} -Future takeScreenshot(FlutterDriver driver, String name) async { +Future _takeScreenshot(FlutterDriver driver, String name) async { final pixels = await driver.screenshot(); - final file = File('$directory/$screenshotLocale-$name.png'); + final file = File('$outputDirectory/$_languageCode-$name.png'); await file.writeAsBytes(pixels); print('* saved screenshot to ${file.path}'); } -void setLanguage(String locale) { +void setLanguage(String languageCode) { test('set language', () async { await driver.tapKeyAndWait('appbar-leading-button'); await driver.tapKeyAndWait('drawer-settings-button'); await driver.tapKeyAndWait('section-language'); await driver.tapKeyAndWait('tile-language'); - await driver.tapKeyAndWait(locale); - screenshotLocale = locale; + await driver.tapKeyAndWait(languageCode); + _languageCode = languageCode; + + await pressDeviceBackButton(); + await driver.waitUntilNoTransientCallbacks(); + }); +} + +void configureCollectionVisibility(AppDebugAction action) { + test('configure collection visibility', () async { + await driver.tapKeyAndWait('appbar-leading-button'); + await driver.tapKeyAndWait('drawer-debug'); + + await driver.tapKeyAndWait('appbar-menu-button'); + await driver.tapKeyAndWait('menu-${action.name}'); await pressDeviceBackButton(); await driver.waitUntilNoTransientCallbacks(); @@ -72,12 +99,12 @@ void setLanguage(String locale) { void collection() { test('1. Collection', () async { - // TODO TLAD hidden filters: reverse of TagFilter('aves-screenshot-collection') - await driver.tapKeyAndWait('appbar-leading-button'); - await driver.tapKeyAndWait('drawer-type-null'); + await driver.tapKeyAndWait('drawer-type-favourite'); + await _search('birds', 'tag-birds'); + await _search('South Korea', 'tag-South Korea'); - await takeScreenshot(driver, '1-collection'); + await _takeScreenshot(driver, '1-collection'); }); } @@ -85,13 +112,9 @@ void viewer() { test('2. Viewer', () async { const query = 'Singapore 087 Zoo - Douc langur'; - await driver.tapKeyAndWait('menu-searchCollection'); - await driver.tap(find.byType('TextField')); - await driver.enterText(query); - final queryChip = find.byValueKey('query-$query'); - await driver.waitFor(queryChip); - await driver.tap(queryChip); - await driver.waitUntilNoTransientCallbacks(); + await driver.tapKeyAndWait('appbar-leading-button'); + await driver.tapKeyAndWait('drawer-type-null'); + await _search(query, 'query-$query'); // delay to avoid flaky descendant resolution await Future.delayed(const Duration(seconds: 2)); @@ -107,7 +130,7 @@ void viewer() { await driver.doubleTap(imageView); await Future.delayed(const Duration(seconds: 1)); - await takeScreenshot(driver, '2-viewer'); + await _takeScreenshot(driver, '2-viewer'); }); } @@ -118,7 +141,7 @@ void info() { await driver.scroll(verticalPageView, 0, -600, const Duration(milliseconds: 400)); await Future.delayed(const Duration(seconds: 2)); - await takeScreenshot(driver, '3-info-basic'); + await _takeScreenshot(driver, '3-info-basic'); await driver.scroll(verticalPageView, 0, -800, const Duration(milliseconds: 600)); await Future.delayed(const Duration(seconds: 1)); @@ -130,7 +153,7 @@ void info() { await driver.tap(gpsTile); await driver.waitUntilNoTransientCallbacks(); - await takeScreenshot(driver, '3-info-metadata'); + await _takeScreenshot(driver, '3-info-metadata'); await pressDeviceBackButton(); await driver.waitUntilNoTransientCallbacks(); @@ -142,15 +165,13 @@ void info() { void stats() { test('5. Stats', () async { - // TODO TLAD hidden filters: PathFilter('/storage/emulated/0/Pictures/Dev') - await driver.tapKeyAndWait('appbar-leading-button'); await driver.tapKeyAndWait('drawer-type-null'); await driver.tapKeyAndWait('appbar-menu-button'); await driver.tapKeyAndWait('menu-stats'); - await takeScreenshot(driver, '5-stats'); + await _takeScreenshot(driver, '5-stats'); await pressDeviceBackButton(); await driver.waitUntilNoTransientCallbacks(); @@ -159,15 +180,9 @@ void stats() { void countries() { test('6. Countries', () async { - // TODO TLAD hidden filters: reverse of TagFilter('aves-screenshot-collection') - // TODO TLAD OR 1) set country covers, 2) hidden filters: PathFilter('/storage/emulated/0/Pictures/Dev') - await driver.tapKeyAndWait('appbar-leading-button'); await driver.tapKeyAndWait('drawer-page-/countries'); - await takeScreenshot(driver, '6-countries'); - - await pressDeviceBackButton(); - await driver.waitUntilNoTransientCallbacks(); + await _takeScreenshot(driver, '6-countries'); }); } diff --git a/test_driver/driver_shaders.dart b/test_driver/driver_shaders.dart index 11a1be7b8..e8fe57ad6 100644 --- a/test_driver/driver_shaders.dart +++ b/test_driver/driver_shaders.dart @@ -3,39 +3,25 @@ import 'dart:ui'; import 'package:aves/main_play.dart' as app; import 'package:aves/model/settings/enums.dart'; import 'package:aves/model/settings/settings.dart'; -import 'package:aves/services/media/media_store_service.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:path/path.dart' as p; -import 'constants.dart'; - -void main() { - enableFlutterDriverExtension(); - - // scan files copied from test assets - // we do it via the app instead of broadcasting via ADB - // because `MEDIA_SCANNER_SCAN_FILE` intent got deprecated in API 29 - PlatformMediaStoreService() - ..scanFile(p.join(targetPicturesDir, 'aves_logo.svg'), 'image/svg+xml') - ..scanFile(p.join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg'); - - // something like `configure().then((_) => app.main());` does not behave as expected - // and starts the app without waiting for `configure` to complete - configureAndLaunch(); -} +void main() => configureAndLaunch(); Future configureAndLaunch() async { + enableFlutterDriverExtension(); await settings.init(monitorPlatformSettings: false); settings - ..keepScreenOn = KeepScreenOn.always + // app ..hasAcceptedTerms = false - ..isErrorReportingAllowed = false ..isInstalledAppAccessAllowed = true + ..isErrorReportingAllowed = false ..locale = const Locale('en') + ..keepScreenOn = KeepScreenOn.always ..homePage = HomePageSetting.collection - ..infoMapStyle = EntryMapStyle.googleNormal - ..imageBackground = EntryBackground.checkered; - + // viewer + ..imageBackground = EntryBackground.checkered + // info + ..infoMapStyle = EntryMapStyle.googleNormal; app.main(); } diff --git a/test_driver/driver_shaders_test.dart b/test_driver/driver_shaders_test.dart index 674d35ae0..92d3cfb7d 100644 --- a/test_driver/driver_shaders_test.dart +++ b/test_driver/driver_shaders_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_driver/flutter_driver.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; -import 'constants.dart'; +import 'common_test.dart'; import 'utils/adb_utils.dart'; import 'utils/driver_extension.dart'; @@ -15,7 +15,7 @@ late FlutterDriver driver; void main() { group('[Aves app]', () { setUpAll(() async { - await copyContent(sourcePicturesDir, targetPicturesDir); + await copyContent(shadersSourcePicturesDir, shadersTargetPicturesDir); await Future.forEach( [ 'deckers.thibault.aves.debug', @@ -29,10 +29,11 @@ void main() { }); tearDownAll(() async { - await removeDirectory(targetPicturesDir); + await removeDirectory(shadersTargetPicturesDir); unawaited(driver.close()); }); + test('scan media dir', () => driver.scanMediaDir(shadersTargetPicturesDirEmulated)); agreeToTerms(); visitAbout(); visitSettings(); @@ -159,7 +160,7 @@ void searchAlbum() { test('[collection] search album', () async { await driver.tapKeyAndWait('menu-searchCollection'); - const albumPath = targetPicturesDirEmulated; + const albumPath = shadersTargetPicturesDirEmulated; final albumDisplayName = p.split(albumPath).last; await driver.tap(find.byType('TextField')); await driver.enterText(albumDisplayName); diff --git a/test_driver/utils/driver_extension.dart b/test_driver/utils/driver_extension.dart index 6be8b9c7f..6b6c18b6b 100644 --- a/test_driver/utils/driver_extension.dart +++ b/test_driver/utils/driver_extension.dart @@ -1,5 +1,8 @@ +import 'package:aves/widgets/debug/app_debug_action.dart'; import 'package:flutter_driver/flutter_driver.dart'; +import 'adb_utils.dart'; + extension ExtraFlutterDriver on FlutterDriver { static const doubleTapDelay = Duration(milliseconds: 100); // in [kDoubleTapMinTime = 40 ms, kDoubleTapTimeout = 300 ms] @@ -13,4 +16,21 @@ extension ExtraFlutterDriver on FlutterDriver { await tap(find.byValueKey(key)); await waitUntilNoTransientCallbacks(); } + + Future scanMediaDir(String dir) async { + await tapKeyAndWait('appbar-leading-button'); + await tapKeyAndWait('drawer-debug'); + + await tapKeyAndWait('appbar-menu-button'); + await tapKeyAndWait('menu-${AppDebugAction.mediaStoreScanDir.name}'); + + await tap(find.byType('TextField')); + await enterText(dir); + + await tap(find.byType('TextButton')); + await waitUntilNoTransientCallbacks(); + + await pressDeviceBackButton(); + await waitUntilNoTransientCallbacks(); + } }