refactor(flutter): remove unreachable state and widget APIs

This commit is contained in:
zarzet committed 2026-09-30 21:44:16 +07:00
1 parent b3f7f59e2e
commit 3a63918e1f
21 files changed
+12 -256

No files matched your search

-1
View File
@@ -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 =>
-4
View File
@@ -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);
}
-2
View File
@@ -114,8 +114,6 @@ class MusicPlayerController {
MusicPlayerHandler? get _handler => musicPlayerHandler;
bool get isAvailable => _handler != null;
DateTime? get sleepTimerEndsAt => _handler?.sleepTimerEndsAt;
Future<MusicPlayerHandler?> ensureInitialized() async {
-24
View File
@@ -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(
-17
View File
@@ -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 {
-5
View File
@@ -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();
-17
View File
@@ -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? ?? '',
-8
View File
@@ -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,
+1 -6
View File
@@ -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.
-2
View File
@@ -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.
-10
View File
@@ -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 -9
View File
@@ -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,
-33
View File
@@ -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.
-6
View File
@@ -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,
-13
View File
@@ -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,
+1 -3
View File
@@ -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),
),
],
),
+3 -10
View File
@@ -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),
-58
View File
@@ -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;
-2
View File
@@ -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);
});
-15
View File
@@ -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,
);
});
});
}