mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-06 08:07:16 +02:00
refactor(flutter): remove unreachable state and widget APIs
This commit is contained in:
1 parent
b3f7f59e2e
commit
3a63918e1f
21 files changed
+12
-256
No files matched your search
@@ -3,7 +3,6 @@ import 'package:flutter/foundation.dart';
|
||||
class AppInfo {
|
||||
static const String version = '5.0.6';
|
||||
static const String buildNumber = '155';
|
||||
static const String fullVersion = '$version+$buildNumber';
|
||||
|
||||
static const String gitCommit = String.fromEnvironment('GIT_COMMIT');
|
||||
static String get shortGitCommit =>
|
||||
|
||||
@@ -224,15 +224,11 @@ class Track {
|
||||
);
|
||||
}
|
||||
|
||||
bool get isFromExtension => source != null && source!.isNotEmpty;
|
||||
|
||||
bool get isDolbyAtmos =>
|
||||
audioModes != null && audioModes!.contains('DOLBY_ATMOS');
|
||||
|
||||
bool get isExplicit => explicit == true;
|
||||
|
||||
bool get hasAudioQuality => audioQuality != null && audioQuality!.isNotEmpty;
|
||||
|
||||
bool get hasPreview => previewUrl != null && previewUrl!.isNotEmpty;
|
||||
}
|
||||
|
||||
|
||||
@@ -167,11 +167,6 @@ class UserPlaylistCollection {
|
||||
);
|
||||
}
|
||||
|
||||
bool containsTrack(Track track) {
|
||||
final key = trackCollectionKey(track);
|
||||
return _trackKeys.contains(key);
|
||||
}
|
||||
|
||||
bool containsTrackKey(String trackKey) {
|
||||
return _trackKeys.contains(trackKey);
|
||||
}
|
||||
@@ -333,12 +328,6 @@ class LibraryCollectionsState {
|
||||
return _playlistsById[playlistId];
|
||||
}
|
||||
|
||||
bool playlistContainsTrack(String playlistId, String trackKey) {
|
||||
final playlist = _playlistsById[playlistId];
|
||||
if (playlist == null) return false;
|
||||
return playlist.containsTrackKey(trackKey);
|
||||
}
|
||||
|
||||
bool isTrackInAnyPlaylist(String trackKey) {
|
||||
return _allPlaylistTrackKeys.contains(trackKey);
|
||||
}
|
||||
|
||||
@@ -114,8 +114,6 @@ class MusicPlayerController {
|
||||
|
||||
MusicPlayerHandler? get _handler => musicPlayerHandler;
|
||||
|
||||
bool get isAvailable => _handler != null;
|
||||
|
||||
DateTime? get sleepTimerEndsAt => _handler?.sleepTimerEndsAt;
|
||||
|
||||
Future<MusicPlayerHandler?> ensureInitialized() async {
|
||||
|
||||
@@ -182,26 +182,6 @@ class RecentAccessNotifier extends Notifier<RecentAccessState> {
|
||||
);
|
||||
}
|
||||
|
||||
void recordTrackAccess({
|
||||
required String id,
|
||||
required String name,
|
||||
String? artistName,
|
||||
String? imageUrl,
|
||||
String? providerId,
|
||||
}) {
|
||||
_recordAccess(
|
||||
RecentAccessItem(
|
||||
id: id,
|
||||
name: name,
|
||||
subtitle: artistName,
|
||||
imageUrl: imageUrl,
|
||||
type: RecentAccessType.track,
|
||||
accessedAt: DateTime.now(),
|
||||
providerId: providerId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void recordPlaylistAccess({
|
||||
required String id,
|
||||
required String name,
|
||||
@@ -261,10 +241,6 @@ class RecentAccessNotifier extends Notifier<RecentAccessState> {
|
||||
unawaited(_appStateDb.addHiddenRecentDownloadId(downloadId));
|
||||
}
|
||||
|
||||
bool isDownloadHidden(String downloadId) {
|
||||
return state.hiddenDownloadIds.contains(downloadId);
|
||||
}
|
||||
|
||||
Future<void> clearHistory() async {
|
||||
final clearedAt = await _appStateDb.clearAllRecentAccess();
|
||||
state = state.copyWith(
|
||||
|
||||
@@ -40,23 +40,6 @@ class RepoCategory {
|
||||
lyrics,
|
||||
integration,
|
||||
];
|
||||
|
||||
static String getDisplayName(String category) {
|
||||
switch (category) {
|
||||
case metadata:
|
||||
return 'Metadata';
|
||||
case download:
|
||||
return 'Download';
|
||||
case utility:
|
||||
return 'Utility';
|
||||
case lyrics:
|
||||
return 'Lyrics';
|
||||
case integration:
|
||||
return 'Integration';
|
||||
default:
|
||||
return category;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RepoExtension {
|
||||
|
||||
@@ -76,11 +76,6 @@ class ThemeNotifier extends Notifier<ThemeSettings> {
|
||||
await _saveToStorage();
|
||||
}
|
||||
|
||||
Future<void> setSeedColorValue(int colorValue) async {
|
||||
state = state.copyWith(seedColorValue: colorValue);
|
||||
await _saveToStorage();
|
||||
}
|
||||
|
||||
Future<void> setUseAmoled(bool value) async {
|
||||
state = state.copyWith(useAmoled: value);
|
||||
await _saveToStorage();
|
||||
|
||||
@@ -572,23 +572,6 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
state = state.copyWith(isShowingRecentAccess: showing);
|
||||
}
|
||||
|
||||
void setTracksFromCollection({
|
||||
required List<Track> tracks,
|
||||
String? albumName,
|
||||
String? playlistName,
|
||||
String? coverUrl,
|
||||
}) {
|
||||
state = TrackState(
|
||||
tracks: tracks,
|
||||
isLoading: false,
|
||||
albumName: albumName,
|
||||
playlistName: playlistName,
|
||||
coverUrl: coverUrl,
|
||||
hasSearchText: state.hasSearchText,
|
||||
isShowingRecentAccess: state.isShowingRecentAccess,
|
||||
);
|
||||
}
|
||||
|
||||
ArtistAlbum _parseArtistAlbum(Map<String, dynamic> data) {
|
||||
return ArtistAlbum(
|
||||
id: data['id'] as String? ?? '',
|
||||
|
||||
@@ -294,14 +294,6 @@ class BackupService {
|
||||
);
|
||||
}
|
||||
|
||||
/// Legacy JSON writer retained for compatibility with callers outside the
|
||||
/// settings UI. It no longer defines the default backup format.
|
||||
static Future<File> writeBackupFile(Map<String, dynamic> envelope) async {
|
||||
final output = await _newBackupFile();
|
||||
await output.writeAsString(encode(envelope), flush: true);
|
||||
return output;
|
||||
}
|
||||
|
||||
static Future<BackupBundle?> parseFile(
|
||||
String path, {
|
||||
Directory? temporaryDirectory,
|
||||
|
||||
@@ -168,14 +168,9 @@ Future<String> validateOrFixIosPath(
|
||||
/// Detailed result for iOS path validation
|
||||
class IosPathValidationResult {
|
||||
final bool isValid;
|
||||
final String? correctedPath;
|
||||
final String? errorReason;
|
||||
|
||||
const IosPathValidationResult({
|
||||
required this.isValid,
|
||||
this.correctedPath,
|
||||
this.errorReason,
|
||||
});
|
||||
const IosPathValidationResult({required this.isValid, this.errorReason});
|
||||
}
|
||||
|
||||
/// Validates an iOS path and returns detailed information about the result.
|
||||
|
||||
@@ -60,8 +60,6 @@ class ProgressStreamPoller<T> {
|
||||
int _errorCount = 0;
|
||||
int _generation = 0;
|
||||
|
||||
bool get usingStream => _usingStream;
|
||||
|
||||
/// (Re)starts progress consumption. When [useStream] is true, attaches the
|
||||
/// stream (falling back to polling on timeout/error); otherwise starts
|
||||
/// polling immediately.
|
||||
|
||||
@@ -114,13 +114,3 @@ double syncedLyricSegmentLift({
|
||||
final emphasis = wordLift > 0 ? normalLift / wordLift : 0;
|
||||
return normalLift + 1.3 * heldStrength * rise * settle * emphasis;
|
||||
}
|
||||
|
||||
/// Horizontal leading edge for a highlight that fills left to right.
|
||||
double syncedLyricsLeftToRightBoundary({
|
||||
required double left,
|
||||
required double right,
|
||||
required double progress,
|
||||
}) {
|
||||
final value = progress.clamp(0.0, 1.0);
|
||||
return left + ((right - left) * value);
|
||||
}
|
||||
@@ -7,14 +7,13 @@ import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
/// [index] controls the stagger delay (each item delayed by [staggerDelay]).
|
||||
/// Set [animate] to false to skip the animation (e.g. when scrolling back).
|
||||
class StaggeredListItem extends StatelessWidget {
|
||||
static const int _defaultMaxAnimatedItems = 10;
|
||||
static const int _maxAnimatedItems = 10;
|
||||
|
||||
final int index;
|
||||
final Widget child;
|
||||
final Duration duration;
|
||||
final Duration staggerDelay;
|
||||
final bool animate;
|
||||
final int maxAnimatedItems;
|
||||
|
||||
const StaggeredListItem({
|
||||
super.key,
|
||||
@@ -23,17 +22,16 @@ class StaggeredListItem extends StatelessWidget {
|
||||
this.duration = const Duration(milliseconds: 250),
|
||||
this.staggerDelay = const Duration(milliseconds: 40),
|
||||
this.animate = true,
|
||||
this.maxAnimatedItems = _defaultMaxAnimatedItems,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!animate ||
|
||||
index >= maxAnimatedItems ||
|
||||
index >= _maxAnimatedItems ||
|
||||
MediaQuery.disableAnimationsOf(context)) {
|
||||
return child;
|
||||
}
|
||||
final cappedIndex = index.clamp(0, maxAnimatedItems - 1);
|
||||
final cappedIndex = index.clamp(0, _maxAnimatedItems - 1);
|
||||
final delay = staggerDelay * cappedIndex;
|
||||
final totalDuration = duration + delay;
|
||||
|
||||
@@ -545,14 +543,14 @@ class ArtistHeaderActionsSkeleton extends StatelessWidget {
|
||||
/// discography loads: optional cover placeholder, "Popular" section, and the
|
||||
/// horizontal album sections.
|
||||
class ArtistScreenSkeleton extends StatelessWidget {
|
||||
final int popularCount;
|
||||
static const int _popularCount = 5;
|
||||
|
||||
final int albumCount;
|
||||
final bool showCoverHeader;
|
||||
final bool showPopularSection;
|
||||
|
||||
const ArtistScreenSkeleton({
|
||||
super.key,
|
||||
this.popularCount = 5,
|
||||
this.albumCount = 5,
|
||||
this.showCoverHeader = true,
|
||||
this.showPopularSection = true,
|
||||
@@ -650,7 +648,7 @@ class ArtistScreenSkeleton extends StatelessWidget {
|
||||
padding: EdgeInsets.fromLTRB(20, 24, 20, 12),
|
||||
child: SkeletonBox(width: 110, height: 24, borderRadius: 4),
|
||||
),
|
||||
for (var index = 0; index < popularCount; index++)
|
||||
for (var index = 0; index < _popularCount; index++)
|
||||
_MornyeTrackSkeleton(numbered: false, index: index),
|
||||
],
|
||||
const Padding(
|
||||
@@ -700,7 +698,7 @@ class ArtistScreenSkeleton extends StatelessWidget {
|
||||
padding: EdgeInsets.fromLTRB(16, 24, 16, 12),
|
||||
child: SkeletonBox(width: 110, height: 22, borderRadius: 4),
|
||||
),
|
||||
...List.generate(popularCount, (index) {
|
||||
...List.generate(_popularCount, (index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
|
||||
@@ -42,39 +42,6 @@ class AppSheetHandle extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// A modal-sheet surface whose height and inner scroll position share one drag.
|
||||
///
|
||||
/// The child must attach the supplied [ScrollController] to its primary
|
||||
/// vertical scroll view. Pulling down at the top then moves the whole surface;
|
||||
/// releasing either restores it or dismisses it at the minimum extent.
|
||||
class AppDraggableSheet extends StatelessWidget {
|
||||
const AppDraggableSheet({
|
||||
super.key,
|
||||
required this.builder,
|
||||
this.initialChildSize = 0.88,
|
||||
this.minChildSize = 0.25,
|
||||
this.maxChildSize = 0.88,
|
||||
});
|
||||
|
||||
final ScrollableWidgetBuilder builder;
|
||||
final double initialChildSize;
|
||||
final double minChildSize;
|
||||
final double maxChildSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
snap: true,
|
||||
shouldCloseOnMinExtent: true,
|
||||
initialChildSize: initialChildSize,
|
||||
minChildSize: minChildSize,
|
||||
maxChildSize: maxChildSize,
|
||||
builder: builder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard chrome for modal sheet content: drag handle, optional title block,
|
||||
/// height cap, keyboard inset and bottom safe area, so each sheet only supplies
|
||||
/// its own body.
|
||||
|
||||
@@ -20,7 +20,6 @@ class CachedCoverImage extends StatelessWidget {
|
||||
final Widget Function(BuildContext, String, Object)? errorWidget;
|
||||
final Widget Function(BuildContext, String)? placeholder;
|
||||
final BorderRadius? borderRadius;
|
||||
final bool resizeDiskCache;
|
||||
final Duration fadeInDuration;
|
||||
final Duration fadeOutDuration;
|
||||
|
||||
@@ -36,7 +35,6 @@ class CachedCoverImage extends StatelessWidget {
|
||||
this.errorWidget,
|
||||
this.placeholder,
|
||||
this.borderRadius,
|
||||
this.resizeDiskCache = false,
|
||||
this.fadeInDuration = Duration.zero,
|
||||
this.fadeOutDuration = Duration.zero,
|
||||
});
|
||||
@@ -75,8 +73,6 @@ class CachedCoverImage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
final diskCacheWidth = resizeDiskCache ? autoMemCacheWidth : null;
|
||||
final diskCacheHeight = resizeDiskCache ? autoMemCacheHeight : null;
|
||||
final image = CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
width: width,
|
||||
@@ -85,8 +81,6 @@ class CachedCoverImage extends StatelessWidget {
|
||||
alignment: alignment,
|
||||
memCacheWidth: autoMemCacheWidth,
|
||||
memCacheHeight: autoMemCacheHeight,
|
||||
maxWidthDiskCache: diskCacheWidth,
|
||||
maxHeightDiskCache: diskCacheHeight,
|
||||
cacheManager: CoverCacheManager.instance,
|
||||
fadeInDuration: fadeInDuration,
|
||||
fadeOutDuration: fadeOutDuration,
|
||||
|
||||
@@ -132,7 +132,6 @@ class MornyeGlassPanel extends ConsumerWidget {
|
||||
this.tintOpacity,
|
||||
this.tintColor,
|
||||
this.backdropFilter,
|
||||
this.liquidGlass = false,
|
||||
this.blurEnabled = true,
|
||||
});
|
||||
|
||||
@@ -146,7 +145,6 @@ class MornyeGlassPanel extends ConsumerWidget {
|
||||
this.tintOpacity = 0.78,
|
||||
this.tintColor,
|
||||
this.backdropFilter,
|
||||
this.liquidGlass = false,
|
||||
this.blurEnabled = true,
|
||||
}) : strongTint = false;
|
||||
|
||||
@@ -158,7 +156,6 @@ class MornyeGlassPanel extends ConsumerWidget {
|
||||
final double? tintOpacity;
|
||||
final Color? tintColor;
|
||||
final ImageFilter? backdropFilter;
|
||||
final bool liquidGlass;
|
||||
|
||||
/// Disable backdrop sampling for surfaces that scroll over a plain page.
|
||||
final bool blurEnabled;
|
||||
@@ -170,16 +167,6 @@ class MornyeGlassPanel extends ConsumerWidget {
|
||||
!MediaQuery.highContrastOf(context) &&
|
||||
(!ref.watch(lowEndDeviceProvider) ||
|
||||
ref.watch(backdropBlurEnabledProvider));
|
||||
if (liquidGlass) {
|
||||
return MornyeGlass(
|
||||
radius: radius,
|
||||
tintOpacity: tintOpacity,
|
||||
tintColor: tintColor,
|
||||
backdropFilter: backdropFilter,
|
||||
blurEnabled: blur,
|
||||
child: Material(color: Colors.transparent, child: child),
|
||||
);
|
||||
}
|
||||
return MornyeGlass.navigation(
|
||||
radius: radius,
|
||||
firstInGroup: firstInGroup,
|
||||
|
||||
@@ -8,7 +8,6 @@ class PrioritySettingsScaffold extends StatelessWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final String infoText;
|
||||
final String? saveLabel;
|
||||
final EdgeInsetsGeometry descriptionPadding;
|
||||
final List<Widget> slivers;
|
||||
final Future<void> Function() onSave;
|
||||
@@ -23,7 +22,6 @@ class PrioritySettingsScaffold extends StatelessWidget {
|
||||
required this.slivers,
|
||||
required this.onSave,
|
||||
required this.onConfirmDiscard,
|
||||
this.saveLabel,
|
||||
this.descriptionPadding = const EdgeInsets.fromLTRB(16, 4, 16, 8),
|
||||
});
|
||||
|
||||
@@ -66,7 +64,7 @@ class PrioritySettingsScaffold extends StatelessWidget {
|
||||
if (hasChanges)
|
||||
TextButton(
|
||||
onPressed: onSave,
|
||||
child: Text(saveLabel ?? context.l10n.dialogSave),
|
||||
child: Text(context.l10n.dialogSave),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -34,7 +34,6 @@ class TrackCard extends StatelessWidget {
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.margin,
|
||||
this.titleStyle,
|
||||
});
|
||||
|
||||
/// Track number, cover art, or anything else that identifies the row.
|
||||
@@ -64,10 +63,6 @@ class TrackCard extends StatelessWidget {
|
||||
/// aligned with each other.
|
||||
final EdgeInsetsGeometry? margin;
|
||||
|
||||
/// Overrides the title text style for rows that need to signal a different
|
||||
/// state (e.g. a dimmed unavailable track).
|
||||
final TextStyle? titleStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tokens = context.tokens;
|
||||
@@ -140,11 +135,9 @@ class TrackCard extends StatelessWidget {
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style:
|
||||
titleStyle ??
|
||||
theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
|
||||
@@ -556,64 +556,6 @@ void main() {
|
||||
expect(find.text('body'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('draggable content moves as one surface and dismisses', (
|
||||
tester,
|
||||
) async {
|
||||
const sheetKey = ValueKey<String>('scrollable-sheet');
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => ElevatedButton(
|
||||
onPressed: () => showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
// Disable the route recognizer so the draggable surface,
|
||||
// rather than the modal's fallback gesture, is under test.
|
||||
enableDrag: false,
|
||||
builder: (_) => AppDraggableSheet(
|
||||
builder: (_, scrollController) => Material(
|
||||
key: sheetKey,
|
||||
child: ListView(
|
||||
controller: scrollController,
|
||||
children: const [
|
||||
SizedBox(height: 800, child: Text('sheet body')),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text('open'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('open'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(sheetKey), findsOneWidget);
|
||||
|
||||
final initialTop = tester.getTopLeft(find.byKey(sheetKey)).dy;
|
||||
final gesture = await tester.startGesture(
|
||||
tester.getCenter(find.byKey(sheetKey)),
|
||||
);
|
||||
await gesture.moveBy(const Offset(0, 160));
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
tester.getTopLeft(find.byKey(sheetKey)).dy,
|
||||
greaterThan(initialTop + 100),
|
||||
);
|
||||
|
||||
await gesture.moveBy(const Offset(0, 260));
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(sheetKey), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('sheet shape comes from the token scale', (tester) async {
|
||||
final shape =
|
||||
AppTheme.light().bottomSheetTheme.shape! as RoundedRectangleBorder;
|
||||
|
||||
@@ -717,8 +717,6 @@ void main() {
|
||||
expect(album.isPlaylistItem, isFalse);
|
||||
expect(album.isArtistItem, isFalse);
|
||||
expect(album.isCollection, isTrue);
|
||||
expect(album.isFromExtension, isTrue);
|
||||
expect(album.hasAudioQuality, isTrue);
|
||||
expect(album.isDolbyAtmos, isTrue);
|
||||
});
|
||||
|
||||
|
||||
@@ -189,20 +189,5 @@ void main() {
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
test('moves the reveal boundary from left to right', () {
|
||||
expect(
|
||||
syncedLyricsLeftToRightBoundary(left: 10, right: 110, progress: 0),
|
||||
10,
|
||||
);
|
||||
expect(
|
||||
syncedLyricsLeftToRightBoundary(left: 10, right: 110, progress: 0.5),
|
||||
60,
|
||||
);
|
||||
expect(
|
||||
syncedLyricsLeftToRightBoundary(left: 10, right: 110, progress: 1),
|
||||
110,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user