| Age | Commit message (Collapse) | Author |
|
Adds ApiTokensScreen (/settings/api-tokens) so any authenticated user can
list, create (with optional expiry), and revoke their own Bearer API tokens.
The plaintext token is displayed exactly once after creation with a clipboard
copy button. Create and revoke use optimistic UI with proper revert on error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Fix typo _isTriggerring → _isTriggering in AdminRescanScreen
- Replace index-based removeAt with identity-based removeWhere in AdminTrashScreen
- Add .cast<T>() for list results from Future.wait in AdminPermissionsScreen
- Add tap-outside safety comment in _confirmHardDelete
- Fix broken dartdoc reference [RescanScreen] → [AdminRescanScreen]
- Add widget tests for AdminRescanScreen, AdminTrashScreen, AdminPermissionsScreen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Add three admin-only screens with routes and navigation tiles:
- AdminPermissionsScreen: permission matrix (users × sets) with optimistic
grant/revoke checkboxes; admin rows shown as disabled (implicit access).
- AdminRescanScreen: trigger library rescan, poll getScanProgress every 2s
while running, cancel timer in dispose, show live file/set counters.
- AdminTrashScreen: list soft-deleted media with restore + hard-delete
(confirmation dialog); optimistic UI with revert-on-error for both actions.
All three use generation counters for stale-async cancellation and guard
async continuations on mounted. Added error mappers and route constants.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Replace identity-equality optimistic-create revert with index-based
logic (placeholderIdx) to avoid relying on reference equality
- Replace insert(index) delete revert with append to avoid stale-index
position jitter from concurrent mutations
- Remove _RoleBadge key collision; update test to use text finders
- currentUserProvider catch block returns null instead of User(id:0)
to avoid colliding with the optimistic placeholder sentinel; fix
broken comment
- adminUserErrorMessage 400-branch delegates to dioErrorMessage to
eliminate duplicated JSON body-parsing logic
- Add Completer-backed optimistic placeholder visibility test
- _EmptyView: replace magic height SizedBox with LayoutBuilder+Center
- _CreateUserDialogState: inline _buildForm and _buildActions into build()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Add AdminUsersScreen with user list, create dialog, delete confirmation,
and optimistic UI (revert on error) for both create and delete operations.
- Add currentUserProvider (FutureProvider) to resolve the logged-in User
object from token storage + listUsers, used for self-delete gating and
Settings admin section visibility.
- Gate Admin section (Manage Users tile) in SettingsScreen behind
currentUserProvider → isAdmin, providing defence-in-depth alongside
server-side 403 enforcement.
- Add adminUserErrorMessage to error_mappers.dart with 400/403/409 handling.
- Add adminUsers route constant (AppRoutes.adminUsers) and GoRouter entry.
- Add 25 tests in admin_users_screen_test.dart and 3 admin-section tests
in settings_screen_test.dart (397 tests total pass).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- _downloadEpisodeAt: read _episodes fresh inside setState instead of using
the pre-await snapshot, and guard that the index is still valid and the row
still lacks a mediaId, preventing silent overwrites of data refreshed by
_load() during the await.
- Add Set<int> _pendingDownloads to prevent concurrent download API calls when
the user double-taps; visually disable the button while in-flight via new
isLoading parameter on _DownloadButton.
- Extract _EpisodeActionButton shared primitive to eliminate structural
duplication between _PlayButton and _DownloadButton (DRY).
- Fix misleading mediaId null-safety comment to describe the actual guarantee.
- Add comments to episodeToggleErrorMessage and episodeDownloadErrorMessage
explaining why they use action-specific fallback strings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Each episode row now shows a play button (when mediaId is non-null) that
navigates to AudioPlayerScreen, or a download button (when mediaId is null)
that triggers a server-side download via downloadEpisode. On success the
row updates in-place to swap the download button for a play button without
requiring a full page reload. Added episodeDownloadErrorMessage to
error_mappers.dart and extended the test suite to cover all new paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- New ShareViewerScreen (/share/:token): unauthenticated share-viewer that
fetches share metadata via publicApiClientProvider, renders filename, type,
duration, thumbnail, and a Play button routing to the video/audio player.
- New publicApiClientProvider: bare Dio client (no auth interceptors) for the
public share endpoint; shares kPlayerBaseUrl with the authenticated client.
- AndroidManifest: http + https deep-link intent-filters for /share/.* so
Android routes share URLs directly into the app (App Links / autoVerify).
- router.dart: /share/:token bypasses the authentication redirect; guard uses
AppRoutes.shareViewerPrefix constant instead of a raw '/share/' string (DIP).
- app_routes.dart: shareViewer route constant, shareViewerPrefix, shareViewerPath helper.
- error_mappers.dart: shareViewerErrorMessage — 404 invalid/revoked, 410 expired.
- player_api_client.dart: baseUrl getter encapsulates rawDio.options.baseUrl so
screens never access transport internals directly (ISP, DIP).
- Review fixes: OCP icon map in _FallbackThumbnail, LSP explicit baseUrl
overrides in test fakes, DIP shareViewerPrefix constant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
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 <noreply@anthropic.com>
|
|
Settings (8b)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
MediaDetailScreen (5b)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
(ab)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
tests (ua)
- Replace placeholder MediaGridScreen with a full implementation: loads
media via listMedia(setId:), renders a Material 3 2-column grid of
thumbnail cards with filename, type icon, and duration overlay.
- Add thumbnailUrl(int mediaId) to PlayerApiClient so screen files
construct thumbnail URLs without importing Dio (DIP).
- Add mediaErrorMessage() top-level helper to error_mappers.dart
(Open-Closed Principle; matches setsErrorMessage pattern).
- Update router.dart to forward the set name as a route extra so
MediaGridScreen shows the name in the app bar without extra API calls.
- Update home_screen.dart _SetCard tap to pass set name as route extra.
- Add 10 widget tests covering: loading, grid render, tap navigation
to /media/:id, empty state, error state + retry, and pull-to-refresh.
- All 109 tests pass; flutter analyze reports no issues.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
tests (ta)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|