From d40a585b5848cc5bfe788c372a2bd1c028b9308d Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 21 May 2026 23:22:57 +0300 Subject: Implement FolderBrowserScreen with browseSet endpoint and breadcrumb navigation (va) Adds a FolderBrowserScreen that calls browseSet on init and pull-to-refresh, renders subfolders first (with cover via setFolderCoverUrl) then media items, and provides a scrollable breadcrumb bar for navigating the folder hierarchy. Key changes: - player-android/lib/screens/folder_browser_screen.dart: new screen with loading/empty/error/refresh states, generation-counter cancellation, and no Dio import in the screen layer (DIP). - player-android/lib/api/player_api_client.dart: add setFolderCoverUrl() so screens never access rawDio directly for URL construction (DIP). - player-android/lib/utils/duration_formatter.dart: extract shared formatDuration() from MediaGridScreen to eliminate the DRY violation. - player-android/lib/screens/media_grid_screen.dart: delegate to formatDuration() from the shared utility. - player-android/lib/utils/error_mappers.dart: add folderErrorMessage(). - player-android/lib/app_routes.dart: add folderBrowser and folderBrowserPath(). - player-android/lib/router.dart: wire /browse/:setId GoRoute. - player-android/test/screens/folder_browser_screen_test.dart: 16 widget tests covering renders, breadcrumbs, folder/media tap navigation, empty, error, retry, and pull-to-refresh. All 298 tests pass; flutter analyze reports no issues. Co-Authored-By: Claude Sonnet 4.6 --- player-android/lib/api/player_api_client.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'player-android/lib/api') diff --git a/player-android/lib/api/player_api_client.dart b/player-android/lib/api/player_api_client.dart index 7f41ec2..a1f4fe9 100644 --- a/player-android/lib/api/player_api_client.dart +++ b/player-android/lib/api/player_api_client.dart @@ -153,6 +153,19 @@ class PlayerApiClient { String streamUrl(int mediaId) => '${rawDio.options.baseUrl}/api/v1/media/$mediaId/stream'; + /// Returns the URL for the cover image of a set's root or subfolder. + /// + /// Mirrors [thumbnailUrl] and [streamUrl]: the API path + /// `/api/v1/sets/{id}/cover` is kept in one place so the UI layer never + /// needs to access Dio internals or hard-code URL segments (DIP). + /// + /// [folder] is the optional subfolder path (empty or null = set root cover). + String setFolderCoverUrl(int setId, {String? folder}) { + final base = '${rawDio.options.baseUrl}/api/v1/sets/$setId/cover'; + if (folder == null || folder.isEmpty) return base; + return '$base?folder=${Uri.encodeComponent(folder)}'; + } + /// Returns the public share URL for a share [token]. /// /// Mirrors [thumbnailUrl] and [streamUrl]: the share path `/s/{token}` is -- cgit v1.2.3