diff --git a/lib/screens/settings/extensions_page.dart b/lib/screens/settings/extensions_page.dart index 89286a3d..55c16e94 100644 --- a/lib/screens/settings/extensions_page.dart +++ b/lib/screens/settings/extensions_page.dart @@ -434,55 +434,46 @@ String _extensionHealthLabel(BuildContext context, String status) { } } -class _DownloadPriorityItem extends ConsumerWidget { - const _DownloadPriorityItem(); +class _ExtensionPriorityLink extends StatelessWidget { + const _ExtensionPriorityLink({ + required this.icon, + required this.title, + required this.subtitle, + required this.enabled, + required this.onTap, + }); + + final IconData icon; + final String title; + final String subtitle; + final bool enabled; + final VoidCallback onTap; @override - Widget build(BuildContext context, WidgetRef ref) { - final extState = ref.watch(extensionProvider); + Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - final hasDownloadExtensions = extState.extensions.any( - (e) => e.enabled && e.hasDownloadProvider, - ); - if (context.isMornye) { return Opacity( - opacity: hasDownloadExtensions ? 1 : 0.5, + opacity: enabled ? 1 : 0.5, child: SettingsItem( - icon: Icons.download, - title: context.l10n.extensionsDownloadPriority, - subtitle: hasDownloadExtensions - ? context.l10n.extensionsDownloadPrioritySubtitle - : context.l10n.extensionsNoDownloadProvider, - onTap: hasDownloadExtensions - ? () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const ProviderPriorityPage(), - ), - ) - : null, + icon: icon, + title: title, + subtitle: subtitle, + onTap: enabled ? onTap : null, ), ); } final content = InkWell( - onTap: hasDownloadExtensions - ? () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const ProviderPriorityPage(), - ), - ) - : null, + onTap: enabled ? onTap : null, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ AppIcon( - Icons.download, - color: hasDownloadExtensions + icon, + color: enabled ? colorScheme.onSurfaceVariant : colorScheme.outline, ), @@ -492,16 +483,14 @@ class _DownloadPriorityItem extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - context.l10n.extensionsDownloadPriority, + title, style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: hasDownloadExtensions ? null : colorScheme.outline, + color: enabled ? null : colorScheme.outline, ), ), const SizedBox(height: 2), Text( - hasDownloadExtensions - ? context.l10n.extensionsDownloadPrioritySubtitle - : context.l10n.extensionsNoDownloadProvider, + subtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -511,7 +500,7 @@ class _DownloadPriorityItem extends ConsumerWidget { ), AppIcon( Icons.chevron_right, - color: hasDownloadExtensions + color: enabled ? colorScheme.onSurfaceVariant : colorScheme.outline, ), @@ -519,9 +508,31 @@ class _DownloadPriorityItem extends ConsumerWidget { ), ), ); - return SettingsSearchTarget( - label: context.l10n.extensionsDownloadPriority, - child: content, + return SettingsSearchTarget(label: title, child: content); + } +} + +class _DownloadPriorityItem extends ConsumerWidget { + const _DownloadPriorityItem(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final extState = ref.watch(extensionProvider); + final hasDownloadExtensions = extState.extensions.any( + (e) => e.enabled && e.hasDownloadProvider, + ); + + return _ExtensionPriorityLink( + icon: Icons.download, + title: context.l10n.extensionsDownloadPriority, + subtitle: hasDownloadExtensions + ? context.l10n.extensionsDownloadPrioritySubtitle + : context.l10n.extensionsNoDownloadProvider, + enabled: hasDownloadExtensions, + onTap: () => Navigator.push( + context, + MaterialPageRoute(builder: (_) => const ProviderPriorityPage()), + ), ); } } @@ -532,89 +543,25 @@ class _MetadataPriorityItem extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final extState = ref.watch(extensionProvider); - final colorScheme = Theme.of(context).colorScheme; final hasMetadataExtensions = extState.extensions.any( (e) => e.enabled && e.hasMetadataProvider, ); - if (context.isMornye) { - return Opacity( - opacity: hasMetadataExtensions ? 1 : 0.5, - child: SettingsItem( - icon: Icons.search, - title: context.l10n.extensionsMetadataPriority, - subtitle: hasMetadataExtensions - ? context.l10n.extensionsMetadataPrioritySubtitle - : context.l10n.extensionsNoMetadataProvider, - onTap: hasMetadataExtensions - ? () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const MetadataProviderPriorityPage(), - ), - ) - : null, - ), - ); - } - - final content = InkWell( - onTap: hasMetadataExtensions - ? () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const MetadataProviderPriorityPage(), - ), - ) - : null, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - children: [ - AppIcon( - Icons.search, - color: hasMetadataExtensions - ? colorScheme.onSurfaceVariant - : colorScheme.outline, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.extensionsMetadataPriority, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: hasMetadataExtensions ? null : colorScheme.outline, - ), - ), - const SizedBox(height: 2), - Text( - hasMetadataExtensions - ? context.l10n.extensionsMetadataPrioritySubtitle - : context.l10n.extensionsNoMetadataProvider, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - AppIcon( - Icons.chevron_right, - color: hasMetadataExtensions - ? colorScheme.onSurfaceVariant - : colorScheme.outline, - ), - ], + return _ExtensionPriorityLink( + icon: Icons.search, + title: context.l10n.extensionsMetadataPriority, + subtitle: hasMetadataExtensions + ? context.l10n.extensionsMetadataPrioritySubtitle + : context.l10n.extensionsNoMetadataProvider, + enabled: hasMetadataExtensions, + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const MetadataProviderPriorityPage(), ), ), ); - return SettingsSearchTarget( - label: context.l10n.extensionsMetadataPriority, - child: content, - ); } } @@ -624,89 +571,25 @@ class _DownloadFallbackItem extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final extState = ref.watch(extensionProvider); - final colorScheme = Theme.of(context).colorScheme; final hasDownloadExtensions = extState.extensions.any( (e) => e.enabled && e.hasDownloadProvider, ); - if (context.isMornye) { - return Opacity( - opacity: hasDownloadExtensions ? 1 : 0.5, - child: SettingsItem( - icon: Icons.alt_route, - title: context.l10n.extensionsFallbackTitle, - subtitle: hasDownloadExtensions - ? context.l10n.extensionsFallbackSubtitle - : context.l10n.extensionsNoDownloadProvider, - onTap: hasDownloadExtensions - ? () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const DownloadFallbackExtensionsPage(), - ), - ) - : null, - ), - ); - } - - final content = InkWell( - onTap: hasDownloadExtensions - ? () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const DownloadFallbackExtensionsPage(), - ), - ) - : null, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - children: [ - AppIcon( - Icons.alt_route, - color: hasDownloadExtensions - ? colorScheme.onSurfaceVariant - : colorScheme.outline, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.extensionsFallbackTitle, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: hasDownloadExtensions ? null : colorScheme.outline, - ), - ), - const SizedBox(height: 2), - Text( - hasDownloadExtensions - ? context.l10n.extensionsFallbackSubtitle - : context.l10n.extensionsNoDownloadProvider, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ), - AppIcon( - Icons.chevron_right, - color: hasDownloadExtensions - ? colorScheme.onSurfaceVariant - : colorScheme.outline, - ), - ], + return _ExtensionPriorityLink( + icon: Icons.alt_route, + title: context.l10n.extensionsFallbackTitle, + subtitle: hasDownloadExtensions + ? context.l10n.extensionsFallbackSubtitle + : context.l10n.extensionsNoDownloadProvider, + enabled: hasDownloadExtensions, + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const DownloadFallbackExtensionsPage(), ), ), ); - return SettingsSearchTarget( - label: context.l10n.extensionsFallbackTitle, - child: content, - ); } } diff --git a/test/extension_priority_links_test.dart b/test/extension_priority_links_test.dart new file mode 100644 index 00000000..139a5b1a --- /dev/null +++ b/test/extension_priority_links_test.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:spotiflac_android/l10n/app_localizations.dart'; +import 'package:spotiflac_android/models/settings.dart'; +import 'package:spotiflac_android/providers/extension_provider.dart'; +import 'package:spotiflac_android/providers/settings_provider.dart'; +import 'package:spotiflac_android/screens/settings/download_fallback_extensions_page.dart'; +import 'package:spotiflac_android/screens/settings/extensions_page.dart'; +import 'package:spotiflac_android/screens/settings/metadata_provider_priority_page.dart'; +import 'package:spotiflac_android/screens/settings/provider_priority_page.dart'; +import 'package:spotiflac_android/theme/mornye_theme.dart'; +import 'package:spotiflac_android/widgets/settings_group.dart'; + +class _Settings extends SettingsNotifier { + @override + AppSettings build() => const AppSettings(); +} + +class _Extensions extends ExtensionNotifier { + _Extensions(this.extension); + final Extension extension; + + @override + ExtensionState build() => + ExtensionState(isInitialized: true, extensions: [extension]); +} + +void main() { + const extension = Extension( + id: 'example.music', + name: 'example.music', + displayName: 'Example Music', + version: '1.0.0', + description: 'Example provider', + enabled: true, + status: 'loaded', + hasDownloadProvider: true, + hasMetadataProvider: true, + ); + final rows = [ + ('Download Priority', 'Set download service order', ProviderPriorityPage), + ( + 'Metadata Priority', + 'Set search & metadata source order', + MetadataProviderPriorityPage, + ), + ( + 'Fallback Extensions', + 'Choose which installed download extensions can be used as fallback', + DownloadFallbackExtensionsPage, + ), + ]; + for (final mornye in [false, true]) { + for (final (enabled, download, metadata) in [ + (true, true, true), + (false, true, true), + (true, false, true), + (true, true, false), + ]) { + for (final (title, subtitle, destination) in rows) { + testWidgets('$title ($mornye, $enabled, $download, $metadata)', ( + tester, + ) async { + final available = + enabled && + (destination == MetadataProviderPriorityPage + ? metadata + : download); + await tester.pumpWidget( + ProviderScope( + overrides: [ + settingsProvider.overrideWith(_Settings.new), + extensionProvider.overrideWith( + () => _Extensions( + extension.copyWith( + enabled: enabled, + hasDownloadProvider: download, + hasMetadataProvider: metadata, + ), + ), + ), + ], + child: MaterialApp( + theme: mornye + ? MornyeTheme.build(Brightness.dark) + : ThemeData(), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: SettingsSearchHighlightScope( + targetLabel: title, + child: const ExtensionsPage(), + ), + ), + ), + ); + await tester.pumpAndSettle(); + final target = find.byWidgetPredicate( + (widget) => widget is SettingsSearchTarget && widget.label == title, + ); + expect(target, findsOneWidget); + expect( + find.byKey(ValueKey('settings-highlight:$title')), + findsOneWidget, + ); + expect(find.text(title).hitTestable(), findsOneWidget); + expect( + find.descendant( + of: target, + matching: find.text( + available + ? subtitle + : 'No extensions with ${destination == MetadataProviderPriorityPage ? 'metadata' : 'download'} provider', + ), + ), + findsOneWidget, + ); + final settingsItem = find.ancestor( + of: target, + matching: find.byType(SettingsItem), + ); + expect(settingsItem, mornye ? findsOneWidget : findsNothing); + if (mornye) { + final opacity = find.ancestor( + of: settingsItem, + matching: find.byType(Opacity), + ); + expect( + tester.widget(opacity.first).opacity, + available ? 1 : 0.5, + ); + } + await tester.tap(find.text(title)); + await tester.pumpAndSettle(); + expect( + find.byType(destination), + available ? findsOneWidget : findsNothing, + ); + expect( + find.byType(ExtensionsPage), + available ? findsNothing : findsOneWidget, + ); + expect(tester.takeException(), isNull); + await tester.pumpWidget(const SizedBox.shrink()); + }); + } + } + } +}