From aa2cdef1d24b6ab265c89776231f990a5cff175b Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 12 Jul 2026 09:35:11 +0700 Subject: [PATCH] refactor: remove dead code across Dart and Go layers - drop unused widgets (AnimatedStateSwitcher, GridSkeleton, adjacentHorizontalPageRoute, buildRemovalAnimation, SwingIcon, BottomSheetOptionTile) and empty _buildInfoCard placeholders - drop test-only Go wrapper methods (GetDownloadURL, MatchTrack, CheckAvailability 7-arg, CustomSearchForItemID) and their parsers - drop unused httputil getters/validators and hand-rolled base64 decoder in favor of encoding/base64 - remove unused riverpod_generator dev dependency --- go_backend/audio_metadata.go | 77 ++------- go_backend/audio_metadata_supplement_test.go | 6 - go_backend/extension_goja_convert.go | 31 ---- .../extension_provider_supplement_test.go | 23 +-- go_backend/extension_provider_types.go | 7 - go_backend/extension_provider_wrapper.go | 114 -------------- go_backend/extension_providers_test.go | 9 -- go_backend/httputil.go | 39 ----- go_backend/httputil_supplement_test.go | 15 -- go_backend/misc_coverage_supplement_test.go | 4 +- lib/screens/album_screen.dart | 5 - lib/screens/downloaded_album_screen.dart | 9 -- lib/screens/local_album_screen.dart | 9 -- lib/screens/main_shell.dart | 54 ------- lib/screens/playlist_screen.dart | 5 - lib/widgets/animation_utils.dart | 147 ------------------ lib/widgets/bottom_sheet_option_tile.dart | 40 ----- pubspec.lock | 32 ---- pubspec.yaml | 1 - 19 files changed, 16 insertions(+), 611 deletions(-) delete mode 100644 lib/widgets/bottom_sheet_option_tile.dart diff --git a/go_backend/audio_metadata.go b/go_backend/audio_metadata.go index ce791dd7..9a8a7140 100644 --- a/go_backend/audio_metadata.go +++ b/go_backend/audio_metadata.go @@ -2,6 +2,7 @@ package gobackend import ( "bytes" + "encoding/base64" "encoding/binary" "fmt" "io" @@ -1460,13 +1461,20 @@ func extractPictureFromVorbisComments(data []byte) ([]byte, string) { key := "METADATA_BLOCK_PICTURE=" if len(comment) > len(key) && strings.ToUpper(string(comment[:len(key)])) == key { - b64Data := comment[len(key):] - decoded := make([]byte, base64StdDecodeLen(len(b64Data))) - n, err := base64StdDecode(decoded, b64Data) + cleaned := strings.Map(func(r rune) rune { + switch r { + case '\n', '\r', ' ', '\t': + return -1 + } + return r + }, string(comment[len(key):])) + decoded, err := base64.StdEncoding.DecodeString(cleaned) + if err != nil { + decoded, err = base64.RawStdEncoding.DecodeString(cleaned) + } if err != nil { continue } - decoded = decoded[:n] imageData, mimeType := parseFLACPictureBlock(decoded) if len(imageData) > 0 { @@ -1520,67 +1528,6 @@ func parseFLACPictureBlock(data []byte) ([]byte, string) { return imageData, mimeType } -func base64StdDecodeLen(n int) int { - return n * 6 / 8 -} - -func base64StdDecode(dst, src []byte) (int, error) { - const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - - decodeMap := make([]byte, 256) - for i := range decodeMap { - decodeMap[i] = 0xFF - } - for i := 0; i < len(alphabet); i++ { - decodeMap[alphabet[i]] = byte(i) - } - - si, di := 0, 0 - for si < len(src) { - for si < len(src) && (src[si] == '\n' || src[si] == '\r' || src[si] == ' ' || src[si] == '\t') { - si++ - } - if si >= len(src) { - break - } - - var vals [4]byte - var valCount int - for valCount < 4 && si < len(src) { - c := src[si] - si++ - if c == '=' { - vals[valCount] = 0 - valCount++ - } else if c == '\n' || c == '\r' || c == ' ' || c == '\t' { - continue - } else if decodeMap[c] != 0xFF { - vals[valCount] = decodeMap[c] - valCount++ - } - } - - if valCount < 2 { - break - } - - if di < len(dst) { - dst[di] = vals[0]<<2 | vals[1]>>4 - di++ - } - if valCount >= 3 && di < len(dst) { - dst[di] = vals[1]<<4 | vals[2]>>2 - di++ - } - if valCount >= 4 && di < len(dst) { - dst[di] = vals[2]<<6 | vals[3] - di++ - } - } - - return di, nil -} - func extractAnyCoverArt(filePath string) ([]byte, string, error) { return extractAnyCoverArtWithHint(filePath, "") } diff --git a/go_backend/audio_metadata_supplement_test.go b/go_backend/audio_metadata_supplement_test.go index 9eca758e..6a87ec15 100644 --- a/go_backend/audio_metadata_supplement_test.go +++ b/go_backend/audio_metadata_supplement_test.go @@ -164,12 +164,6 @@ func TestAudioMetadataCoverAndQualityHelpers(t *testing.T) { if commentMIME != "image/png" || !bytes.Equal(commentImage, png) { t.Fatalf("vorbis picture = %s/%v", commentMIME, commentImage) } - decoded := make([]byte, base64StdDecodeLen(len("SGV sbG8="))+4) - n, err := base64StdDecode(decoded, []byte("SGV sbG8=")) - if err != nil || strings.TrimRight(string(decoded[:n]), "\x00") != "Hello" { - t.Fatalf("base64 decode = %q/%v", decoded[:n], err) - } - if detectOggStreamType([][]byte{[]byte("OpusHeadxxxx")}) != oggStreamOpus { t.Fatal("expected opus stream") } diff --git a/go_backend/extension_goja_convert.go b/go_backend/extension_goja_convert.go index c304000e..dc2ed3bf 100644 --- a/go_backend/extension_goja_convert.go +++ b/go_backend/extension_goja_convert.go @@ -57,17 +57,6 @@ func gojaObjectInt64(obj *goja.Object, keys ...string) int64 { return 0 } -func gojaObjectFloat(obj *goja.Object, keys ...string) float64 { - for _, key := range keys { - value := obj.Get(key) - if gojaValueIsEmpty(value) { - continue - } - return value.ToFloat() - } - return 0 -} - func gojaObjectBool(obj *goja.Object, keys ...string) bool { for _, key := range keys { value := obj.Get(key) @@ -467,16 +456,6 @@ func parseExtensionAvailabilityValue(vm *goja.Runtime, value goja.Value) ExtAvai } } -func parseExtensionDownloadURLValue(vm *goja.Runtime, value goja.Value) ExtDownloadURLResult { - obj := value.ToObject(vm) - return ExtDownloadURLResult{ - URL: gojaObjectString(obj, "url"), - Format: gojaObjectString(obj, "format"), - BitDepth: gojaObjectInt(obj, "bit_depth", "bitDepth"), - SampleRate: gojaObjectInt(obj, "sample_rate", "sampleRate"), - } -} - func parseExtensionDownloadDecryptionValue(vm *goja.Runtime, value goja.Value) *DownloadDecryptionInfo { if gojaValueIsEmpty(value) { return nil @@ -577,16 +556,6 @@ func parseExtensionURLHandleValue(vm *goja.Runtime, value goja.Value) (ExtURLHan return handleResult, nil } -func parseExtensionMatchTrackValue(vm *goja.Runtime, value goja.Value) MatchTrackResult { - obj := value.ToObject(vm) - return MatchTrackResult{ - Matched: gojaObjectBool(obj, "matched"), - TrackID: gojaObjectString(obj, "track_id", "trackId"), - Confidence: gojaObjectFloat(obj, "confidence"), - Reason: gojaObjectString(obj, "reason"), - } -} - func parseExtensionPostProcessValue(vm *goja.Runtime, value goja.Value) PostProcessResult { obj := value.ToObject(vm) return PostProcessResult{ diff --git a/go_backend/extension_provider_supplement_test.go b/go_backend/extension_provider_supplement_test.go index 8a02d1bb..c1fe515b 100644 --- a/go_backend/extension_provider_supplement_test.go +++ b/go_backend/extension_provider_supplement_test.go @@ -57,22 +57,14 @@ func TestExtensionProviderWrapperFullSurface(t *testing.T) { t.Fatalf("enriched = %#v", enriched) } - availability, err := provider.CheckAvailability("ISRC", "Song", "Artist", "spotify:1", "dz", "tidal", "qobuz") + availability, err := provider.CheckAvailabilityForItemID("ISRC", "Song", "Artist", "spotify:1", "dz", "tidal", "qobuz", 0, "") if err != nil { - t.Fatalf("CheckAvailability: %v", err) + t.Fatalf("CheckAvailabilityForItemID: %v", err) } if !availability.Available || availability.TrackID != "download-track" || !availability.SkipFallback { t.Fatalf("availability = %#v", availability) } - downloadURL, err := provider.GetDownloadURL("track-1", "LOSSLESS") - if err != nil { - t.Fatalf("GetDownloadURL: %v", err) - } - if downloadURL.Format != "flac" || downloadURL.BitDepth != 24 || downloadURL.SampleRate != 96000 { - t.Fatalf("download URL = %#v", downloadURL) - } - progress := []int{} download, err := provider.Download("track-1", "LOSSLESS", filepath.Join(t.TempDir(), "song.flac"), "", func(percent int) { progress = append(progress, percent) @@ -100,17 +92,6 @@ func TestExtensionProviderWrapperFullSurface(t *testing.T) { t.Fatalf("url result = %#v", urlResult) } - match, err := provider.MatchTrack( - map[string]any{"name": "Song", "artists": "Artist"}, - []map[string]any{{"id": "download-track", "name": "Song"}}, - ) - if err != nil { - t.Fatalf("MatchTrack: %v", err) - } - if !match.Matched || match.TrackID != "download-track" { - t.Fatalf("match = %#v", match) - } - post, err := provider.PostProcess(filepath.Join(t.TempDir(), "song.flac"), map[string]any{"title": "Song"}, "hook") if err != nil { t.Fatalf("PostProcess: %v", err) diff --git a/go_backend/extension_provider_types.go b/go_backend/extension_provider_types.go index f3c16cbe..bcb5dbe3 100644 --- a/go_backend/extension_provider_types.go +++ b/go_backend/extension_provider_types.go @@ -91,13 +91,6 @@ type ExtAvailabilityResult struct { SkipFallback bool `json:"skip_fallback,omitempty"` } -type ExtDownloadURLResult struct { - URL string `json:"url"` - Format string `json:"format"` - BitDepth int `json:"bit_depth,omitempty"` - SampleRate int `json:"sample_rate,omitempty"` -} - type DownloadDecryptionInfo struct { Strategy string `json:"strategy,omitempty"` Key string `json:"key,omitempty"` diff --git a/go_backend/extension_provider_wrapper.go b/go_backend/extension_provider_wrapper.go index 8a37021b..47c9f4d7 100644 --- a/go_backend/extension_provider_wrapper.go +++ b/go_backend/extension_provider_wrapper.go @@ -413,10 +413,6 @@ func (p *extensionProviderWrapper) EnrichTrackForItemID(track *ExtTrackMetadata, return &enrichedTrack, nil } -func (p *extensionProviderWrapper) CheckAvailability(isrc, trackName, artistName, spotifyID, deezerID, tidalID, qobuzID string) (*ExtAvailabilityResult, error) { - return p.CheckAvailabilityForItemID(isrc, trackName, artistName, spotifyID, deezerID, tidalID, qobuzID, 0, "") -} - func (p *extensionProviderWrapper) CheckAvailabilityForItemID(isrc, trackName, artistName, spotifyID, deezerID, tidalID, qobuzID string, durationMS int, itemID string) (*ExtAvailabilityResult, error) { if !p.extension.Manifest.IsDownloadProvider() { return nil, fmt.Errorf("extension '%s' is not a download provider", p.extension.ID) @@ -488,54 +484,6 @@ func (p *extensionProviderWrapper) CheckAvailabilityForItemID(isrc, trackName, a return &availability, nil } -func (p *extensionProviderWrapper) GetDownloadURL(trackID, quality string) (*ExtDownloadURLResult, error) { - if !p.extension.Manifest.IsDownloadProvider() { - return nil, fmt.Errorf("extension '%s' is not a download provider", p.extension.ID) - } - - if !p.extension.Enabled { - return nil, fmt.Errorf("extension '%s' is disabled", p.extension.ID) - } - perf := newExtensionCallPerf(p.extension.ID, "getDownloadUrl") - defer perf.finish() - initStartedAt := time.Now() - if err := p.lockReadyVM(); err != nil { - return nil, err - } - perf.recordInit(time.Since(initStartedAt)) - defer p.extension.VMMu.Unlock() - - script := fmt.Sprintf(` - (function() { - if (typeof extension !== 'undefined' && typeof extension.getDownloadUrl === 'function') { - return extension.getDownloadUrl(%q, %q); - } - return null; - })() - `, trackID, quality) - - jsStartedAt := time.Now() - result, err := RunWithTimeoutAndRecover(p.vm, script, DefaultJSTimeout) - perf.recordJS(time.Since(jsStartedAt)) - perf.recordPayload(result) - if err != nil { - if IsTimeoutError(err) { - return nil, fmt.Errorf("getDownloadUrl timeout: extension took too long to respond") - } - return nil, fmt.Errorf("getDownloadUrl failed: %w", err) - } - - if result == nil || goja.IsUndefined(result) || goja.IsNull(result) { - return nil, fmt.Errorf("getDownloadUrl returned null") - } - - parseStartedAt := time.Now() - urlResult := parseExtensionDownloadURLValue(p.vm, result) - perf.recordParse(time.Since(parseStartedAt)) - perf.setItems(1) - return &urlResult, nil -} - const ExtDownloadTimeout = DownloadTimeout func (p *extensionProviderWrapper) Download(trackID, quality, outputPath, itemID string, onProgress func(percent int)) (*ExtDownloadResult, error) { @@ -656,10 +604,6 @@ func (p *extensionProviderWrapper) CustomSearchForRequestID(query string, option return p.customSearch(query, options, "", requestID) } -func (p *extensionProviderWrapper) CustomSearchForItemID(query string, options map[string]any, itemID string) ([]ExtTrackMetadata, error) { - return p.customSearch(query, options, itemID, "") -} - func (p *extensionProviderWrapper) customSearch(query string, options map[string]any, itemID, requestID string) ([]ExtTrackMetadata, error) { if !p.extension.Manifest.HasCustomSearch() { return nil, fmt.Errorf("extension '%s' does not support custom search", p.extension.ID) @@ -875,64 +819,6 @@ func (p *extensionProviderWrapper) HandleURL(url string) (*ExtURLHandleResult, e return &handleResult, nil } -type MatchTrackResult struct { - Matched bool `json:"matched"` - TrackID string `json:"track_id,omitempty"` - Confidence float64 `json:"confidence,omitempty"` - Reason string `json:"reason,omitempty"` -} - -func (p *extensionProviderWrapper) MatchTrack(sourceTrack map[string]any, candidates []map[string]any) (*MatchTrackResult, error) { - if !p.extension.Manifest.HasCustomMatching() { - return nil, fmt.Errorf("extension '%s' does not support custom matching", p.extension.ID) - } - - if !p.extension.Enabled { - return nil, fmt.Errorf("extension '%s' is disabled", p.extension.ID) - } - perf := newExtensionCallPerf(p.extension.ID, "matchTrack") - defer perf.finish() - initStartedAt := time.Now() - if err := p.lockReadyVM(); err != nil { - return nil, err - } - perf.recordInit(time.Since(initStartedAt)) - defer p.extension.VMMu.Unlock() - - sourceJSON, _ := json.Marshal(sourceTrack) - candidatesJSON, _ := json.Marshal(candidates) - - script := fmt.Sprintf(` - (function() { - if (typeof extension !== 'undefined' && typeof extension.matchTrack === 'function') { - return extension.matchTrack(%s, %s); - } - return null; - })() - `, string(sourceJSON), string(candidatesJSON)) - - jsStartedAt := time.Now() - result, err := RunWithTimeoutAndRecover(p.vm, script, DefaultJSTimeout) - perf.recordJS(time.Since(jsStartedAt)) - perf.recordPayload(result) - if err != nil { - if IsTimeoutError(err) { - return nil, fmt.Errorf("matchTrack timeout: extension took too long to respond") - } - return nil, fmt.Errorf("matchTrack failed: %w", err) - } - - if result == nil || goja.IsUndefined(result) || goja.IsNull(result) { - return &MatchTrackResult{Matched: false, Reason: "not implemented"}, nil - } - - parseStartedAt := time.Now() - matchResult := parseExtensionMatchTrackValue(p.vm, result) - perf.recordParse(time.Since(parseStartedAt)) - perf.setItems(1) - return &matchResult, nil -} - type PostProcessResult struct { Success bool `json:"success"` NewFilePath string `json:"new_file_path,omitempty"` diff --git a/go_backend/extension_providers_test.go b/go_backend/extension_providers_test.go index 7d994969..6ebdb669 100644 --- a/go_backend/extension_providers_test.go +++ b/go_backend/extension_providers_test.go @@ -745,15 +745,6 @@ func TestParseExtensionURLHandleResult(t *testing.T) { func TestParseExtensionAuxiliaryResults(t *testing.T) { vm := goja.New() - matchValue, err := vm.RunString(`({ matched: true, trackId: "track-1", confidence: 0.92, reason: "isrc" })`) - if err != nil { - t.Fatalf("build match value: %v", err) - } - match := parseExtensionMatchTrackValue(vm, matchValue) - if !match.Matched || match.TrackID != "track-1" || match.Confidence != 0.92 || match.Reason != "isrc" { - t.Fatalf("unexpected match result: %+v", match) - } - postValue, err := vm.RunString(`({ success: true, newFilePath: "/tmp/new.flac", newFileUri: "content://new", bitDepth: 24, sampleRate: 96000 })`) if err != nil { t.Fatalf("build post-process value: %v", err) diff --git a/go_backend/httputil.go b/go_backend/httputil.go index 3169f628..61657c27 100644 --- a/go_backend/httputil.go +++ b/go_backend/httputil.go @@ -125,11 +125,6 @@ var sharedClient = &http.Client{ Timeout: DefaultTimeout, } -var downloadClient = &http.Client{ - Transport: newCompatibilityTransport(sharedTransport), - Timeout: DownloadTimeout, -} - func NewHTTPClientWithTimeout(timeout time.Duration) *http.Client { return &http.Client{ Transport: newCompatibilityTransport(sharedTransport), @@ -144,14 +139,6 @@ func NewMetadataHTTPClient(timeout time.Duration) *http.Client { } } -func GetSharedClient() *http.Client { - return sharedClient -} - -func GetDownloadClient() *http.Client { - return downloadClient -} - func CloseIdleConnections() { sharedTransport.CloseIdleConnections() extensionAPITransport.CloseIdleConnections() @@ -403,32 +390,6 @@ func ReadResponseBody(resp *http.Response) ([]byte, error) { return body, nil } -func ValidateResponse(resp *http.Response) error { - if resp == nil { - return fmt.Errorf("response is nil") - } - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status) - } - - return nil -} - -func BuildErrorMessage(apiURL string, statusCode int, responsePreview string) string { - msg := fmt.Sprintf("API %s failed", apiURL) - if statusCode > 0 { - msg += fmt.Sprintf(" (HTTP %d)", statusCode) - } - if responsePreview != "" { - if len(responsePreview) > 100 { - responsePreview = responsePreview[:100] + "..." - } - msg += fmt.Sprintf(": %s", responsePreview) - } - return msg -} - type ISPBlockingError struct { Domain string Reason string diff --git a/go_backend/httputil_supplement_test.go b/go_backend/httputil_supplement_test.go index 8a79d544..0e388044 100644 --- a/go_backend/httputil_supplement_test.go +++ b/go_backend/httputil_supplement_test.go @@ -26,9 +26,6 @@ func TestHTTPUtilityHelpers(t *testing.T) { if NewHTTPClientWithTimeout(time.Second).Timeout != time.Second || NewMetadataHTTPClient(time.Second).Timeout != time.Second { t.Fatal("client timeout mismatch") } - if GetSharedClient() == nil || GetDownloadClient() == nil { - t.Fatal("expected shared clients") - } if sharedTransport.TLSClientConfig == nil || sharedTransport.TLSClientConfig.RootCAs == nil { t.Fatal("expected supplemental TLS root pool") } @@ -115,18 +112,6 @@ func TestHTTPUtilityHelpers(t *testing.T) { if body, err := ReadResponseBody(&http.Response{Body: io.NopCloser(strings.NewReader("ok"))}); err != nil || string(body) != "ok" { t.Fatalf("ReadResponseBody = %q/%v", body, err) } - if err := ValidateResponse(nil); err == nil { - t.Fatal("expected nil response validation error") - } - if err := ValidateResponse(&http.Response{StatusCode: 404, Status: "404 Not Found"}); err == nil { - t.Fatal("expected bad status validation error") - } - if err := ValidateResponse(&http.Response{StatusCode: 200}); err != nil { - t.Fatalf("ValidateResponse: %v", err) - } - if msg := BuildErrorMessage("api", 500, strings.Repeat("x", 120)); !strings.Contains(msg, "...") { - t.Fatalf("BuildErrorMessage = %q", msg) - } if calculateNextDelay(10*time.Millisecond, RetryConfig{BackoffFactor: 3, MaxDelay: 20 * time.Millisecond}) != 20*time.Millisecond { t.Fatal("calculateNextDelay mismatch") } diff --git a/go_backend/misc_coverage_supplement_test.go b/go_backend/misc_coverage_supplement_test.go index 905aada2..75e53cb5 100644 --- a/go_backend/misc_coverage_supplement_test.go +++ b/go_backend/misc_coverage_supplement_test.go @@ -227,8 +227,8 @@ func TestExtensionHealthInitializeVMAndCustomSearchWrappers(t *testing.T) { cancelMu.Lock() delete(cancelMap, "custom-item-unique") cancelMu.Unlock() - if tracks, err := provider.CustomSearchForItemID("needle", nil, "custom-item-unique"); err != nil || len(tracks) == 0 { - t.Fatalf("CustomSearchForItemID = %#v/%v", tracks, err) + if tracks, err := provider.customSearch("needle", nil, "custom-item-unique", ""); err != nil || len(tracks) == 0 { + t.Fatalf("customSearch (item ID) = %#v/%v", tracks, err) } if healthJSON, err := CheckExtensionHealthJSON(ext.ID); err != nil || !strings.Contains(healthJSON, `"status":"offline"`) { t.Fatalf("CheckExtensionHealthJSON = %q/%v", healthJSON, err) diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index 8da638c1..f9b77a9d 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -455,7 +455,6 @@ class _AlbumScreenState extends ConsumerState { controller: _scrollController, slivers: [ _buildAppBar(context, colorScheme, pageBackgroundColor), - _buildInfoCard(context, colorScheme), if (_isLoading) const SliverToBoxAdapter( child: Padding( @@ -628,10 +627,6 @@ class _AlbumScreenState extends ConsumerState { ); } - Widget _buildInfoCard(BuildContext context, ColorScheme colorScheme) { - return const SliverToBoxAdapter(child: SizedBox.shrink()); - } - Widget _buildAlbumFooter( BuildContext context, ColorScheme colorScheme, diff --git a/lib/screens/downloaded_album_screen.dart b/lib/screens/downloaded_album_screen.dart index 8b7f555e..4f25bcdd 100644 --- a/lib/screens/downloaded_album_screen.dart +++ b/lib/screens/downloaded_album_screen.dart @@ -395,7 +395,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { controller: _scrollController, slivers: [ _buildAppBar(context, colorScheme, tracks), - _buildInfoCard(context, colorScheme, tracks), _buildTrackList(context, colorScheme, tracks), SliverToBoxAdapter( child: SizedBox(height: _isSelectionMode ? 120 : 32), @@ -639,14 +638,6 @@ class _DownloadedAlbumScreenState extends ConsumerState { ); } - Widget _buildInfoCard( - BuildContext context, - ColorScheme colorScheme, - List tracks, - ) { - return const SliverToBoxAdapter(child: SizedBox.shrink()); - } - String? _getCommonQuality(List tracks) { if (identical(tracks, _commonQualitySourceCache)) { return _commonQualityCache; diff --git a/lib/screens/local_album_screen.dart b/lib/screens/local_album_screen.dart index d70f6680..6efb3d7e 100644 --- a/lib/screens/local_album_screen.dart +++ b/lib/screens/local_album_screen.dart @@ -283,7 +283,6 @@ class _LocalAlbumScreenState extends ConsumerState { controller: _scrollController, slivers: [ _buildAppBar(context, colorScheme), - _buildInfoCard(context, colorScheme, tracks), _buildTrackList(context, colorScheme, tracks), SliverToBoxAdapter( child: SizedBox(height: _isSelectionMode ? 120 : 32), @@ -385,14 +384,6 @@ class _LocalAlbumScreenState extends ConsumerState { ); } - Widget _buildInfoCard( - BuildContext context, - ColorScheme colorScheme, - List tracks, - ) { - return const SliverToBoxAdapter(child: SizedBox.shrink()); - } - Widget _metaWhiteItem(IconData? icon, String label) { const textStyle = TextStyle( color: Colors.white, diff --git a/lib/screens/main_shell.dart b/lib/screens/main_shell.dart index fa7ee049..3642347c 100644 --- a/lib/screens/main_shell.dart +++ b/lib/screens/main_shell.dart @@ -812,60 +812,6 @@ class _SlidingIconState extends State } } -class SwingIcon extends StatefulWidget { - final Widget child; - const SwingIcon({super.key, required this.child}); - - @override - State createState() => _SwingIconState(); -} - -class _SwingIconState extends State - with SingleTickerProviderStateMixin { - late AnimationController _controller; - late Animation _rotationAnimation; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - duration: const Duration(milliseconds: 600), - vsync: this, - ); - - _rotationAnimation = TweenSequence([ - TweenSequenceItem(tween: Tween(begin: 0.0, end: -0.2), weight: 20), - TweenSequenceItem(tween: Tween(begin: -0.2, end: 0.15), weight: 20), - TweenSequenceItem(tween: Tween(begin: 0.15, end: -0.1), weight: 20), - TweenSequenceItem(tween: Tween(begin: -0.1, end: 0.05), weight: 20), - TweenSequenceItem(tween: Tween(begin: 0.05, end: 0.0), weight: 20), - ]).animate(_controller); - - _controller.forward(); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _rotationAnimation, - builder: (context, child) { - return Transform.rotate( - angle: _rotationAnimation.value, - alignment: Alignment.topCenter, - child: child, - ); - }, - child: widget.child, - ); - } -} - class SpinIcon extends StatefulWidget { final Widget child; const SpinIcon({super.key, required this.child}); diff --git a/lib/screens/playlist_screen.dart b/lib/screens/playlist_screen.dart index 7ee8f08b..f77e788e 100644 --- a/lib/screens/playlist_screen.dart +++ b/lib/screens/playlist_screen.dart @@ -202,7 +202,6 @@ class _PlaylistScreenState extends ConsumerState { controller: _scrollController, slivers: [ _buildAppBar(context, colorScheme), - _buildInfoCard(context, colorScheme), _buildTrackList(context, colorScheme), _buildPlaylistFooter(context, colorScheme), SliverToBoxAdapter( @@ -296,10 +295,6 @@ class _PlaylistScreenState extends ConsumerState { ); } - Widget _buildInfoCard(BuildContext context, ColorScheme colorScheme) { - return const SliverToBoxAdapter(child: SizedBox.shrink()); - } - String _formatReleaseDate(String date) { if (date.length >= 10) { final parts = date.substring(0, 10).split('-'); diff --git a/lib/widgets/animation_utils.dart b/lib/widgets/animation_utils.dart index 6f1cbcd0..e9b78355 100644 --- a/lib/widgets/animation_utils.dart +++ b/lib/widgets/animation_utils.dart @@ -56,34 +56,6 @@ class StaggeredListItem extends StatelessWidget { } } -/// A convenience wrapper around [AnimatedSwitcher] that crossfades between -/// different widget states (loading, content, empty, error). -/// -/// Assign a unique [ValueKey] to each child so the switcher detects changes. -class AnimatedStateSwitcher extends StatelessWidget { - final Widget child; - final Duration duration; - - const AnimatedStateSwitcher({ - super.key, - required this.child, - this.duration = const Duration(milliseconds: 250), - }); - - @override - Widget build(BuildContext context) { - return AnimatedSwitcher( - duration: duration, - switchInCurve: Curves.easeOut, - switchOutCurve: Curves.easeIn, - transitionBuilder: (child, animation) { - return FadeTransition(opacity: animation, child: child); - }, - child: child, - ); - } -} - /// Creates a platform-aware material route. /// /// This intentionally defers route transitions to Flutter's material route and @@ -93,58 +65,6 @@ Route slidePageRoute({required Widget page}) { return MaterialPageRoute(builder: (context) => page); } -/// A directional horizontal transition for adjacent content, such as moving -/// between next/previous items within the same detail context. -Route adjacentHorizontalPageRoute({ - required Widget page, - required bool fromRight, -}) { - return _AdjacentHorizontalPageRoute( - builder: (context) => page, - fromRight: fromRight, - ); -} - -class _AdjacentHorizontalPageRoute extends MaterialPageRoute { - final bool fromRight; - - _AdjacentHorizontalPageRoute({ - required super.builder, - required this.fromRight, - }); - - @override - Widget buildTransitions( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, - ) { - if (animation.status == AnimationStatus.reverse) { - return super.buildTransitions( - context, - animation, - secondaryAnimation, - child, - ); - } - - final curved = CurvedAnimation( - parent: animation, - curve: Curves.easeOutCubic, - ); - final begin = Offset(fromRight ? 0.22 : -0.22, 0); - - return SlideTransition( - position: Tween(begin: begin, end: Offset.zero).animate(curved), - child: FadeTransition( - opacity: Tween(begin: 0.92, end: 1.0).animate(curved), - child: child, - ), - ); - } -} - /// A shimmer effect widget that can wrap skeleton placeholders. class ShimmerLoading extends StatefulWidget { final Widget child; @@ -331,8 +251,6 @@ class TrackListSkeleton extends StatelessWidget { } } -/// Grid skeleton – mimics a grid of album/playlist cards while loading. - /// Album track list skeleton – mimics the album screen track list layout /// (track number + title + artist + trailing icon, no cover art thumbnail). class AlbumTrackListSkeleton extends StatelessWidget { @@ -470,59 +388,6 @@ class _CollectionHeaderSkeleton extends StatelessWidget { } } -class GridSkeleton extends StatelessWidget { - final int itemCount; - final int crossAxisCount; - - const GridSkeleton({super.key, this.itemCount = 6, this.crossAxisCount = 2}); - - @override - Widget build(BuildContext context) { - return ShimmerLoading( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - mainAxisSpacing: 12, - crossAxisSpacing: 12, - childAspectRatio: 0.75, - ), - itemCount: itemCount, - itemBuilder: (context, index) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Expanded( - child: SkeletonBox( - width: double.infinity, - height: double.infinity, - borderRadius: 12, - ), - ), - const SizedBox(height: 8), - SkeletonBox( - width: 80 + (index % 3) * 20, - height: 12, - borderRadius: 4, - ), - const SizedBox(height: 4), - SkeletonBox( - width: 50 + (index % 2) * 15, - height: 10, - borderRadius: 4, - ), - ], - ); - }, - ), - ), - ); - } -} - /// Artist screen skeleton shown below the SliverAppBar header while the /// discography loads: optional cover placeholder, "Popular" section, and the /// horizontal album sections. @@ -939,15 +804,3 @@ class _AnimatedBadgeState extends State return ScaleTransition(scale: _scaleAnimation, child: widget.child); } } - -/// Build a removal animation for [AnimatedList] items. -/// Use as the `builder` callback in [AnimatedListState.removeItem]. -Widget buildRemovalAnimation(Widget child, Animation animation) { - return SizeTransition( - sizeFactor: CurvedAnimation(parent: animation, curve: Curves.easeInOut), - child: FadeTransition( - opacity: CurvedAnimation(parent: animation, curve: Curves.easeIn), - child: child, - ), - ); -} diff --git a/lib/widgets/bottom_sheet_option_tile.dart b/lib/widgets/bottom_sheet_option_tile.dart deleted file mode 100644 index 7f599a47..00000000 --- a/lib/widgets/bottom_sheet_option_tile.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; - -/// Reusable option tile for bottom sheets. -/// Used in playlist options, track options, cover options, etc. -class BottomSheetOptionTile extends StatelessWidget { - final IconData icon; - final Color? iconColor; - final String title; - final VoidCallback onTap; - - const BottomSheetOptionTile({ - super.key, - required this.icon, - this.iconColor, - required this.title, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - return ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), - leading: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - icon, - color: iconColor ?? colorScheme.onPrimaryContainer, - size: 20, - ), - ), - title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)), - onTap: onTap, - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index fbe7e732..9009992c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -281,14 +281,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" - url: "https://pub.dev" - source: hosted - version: "4.11.1" collection: dependency: transitive description: @@ -829,14 +821,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - mockito: - dependency: transitive - description: - name: mockito - sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 - url: "https://pub.dev" - source: hosted - version: "5.6.4" nm: dependency: transitive description: @@ -1077,22 +1061,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0-dev.10" - riverpod_annotation: - dependency: transitive - description: - name: riverpod_annotation - sha256: "674dbb26e2db3d9253166faf4758c796af14146b8fbcf5e7102bc8a04cd359b8" - url: "https://pub.dev" - source: hosted - version: "4.0.3" - riverpod_generator: - dependency: "direct dev" - description: - name: riverpod_generator - sha256: "54d790c3fee1ae281c448801bfdbfaa9fd961a9d3998494e0fcd9ee32184d7eb" - url: "https://pub.dev" - source: hosted - version: "4.0.4" riverpod_lint: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index bceca833..a908e6c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,7 +74,6 @@ dev_dependencies: sdk: flutter flutter_lints: ^6.0.0 build_runner: ^2.15.0 - riverpod_generator: ^4.0.4 riverpod_lint: ^3.1.4 json_serializable: ^6.14.0 flutter_launcher_icons: ^0.14.3