| Age | Commit message (Collapse) | Author |
|
- Make `Key('api_tokens_copy_snackbar')` const (promoted by outer const SnackBar)
- Fix misleading comment in _revokeToken: mirrors AdminUsersScreen (append
on revert), not MySharesScreen (which uses index-based re-insert)
- Add widget test: submits null expiresInDays when no expiry date selected
- Add unit tests for expiresInDays clamp logic (correct days, min 1, max 36500)
- Skip 403 handling in apiTokenErrorMessage: token endpoints use requireSession
middleware and the service layer never returns ErrForbidden for token ops
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
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>
|
|
- 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>
|
|
Replace Text('') with SizedBox.shrink() in _buildFooter so dead space
and the premature episodes_no_more key are eliminated when more pages
exist. Add testWidgets for the shares_no_more footer key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
pagination tests
- Reset _isLoadingMore = false inside _load()'s setState in both
MediaGridScreen and PodcastEpisodesScreen, so a generation-mismatch
early return in an in-flight _loadMore does not leave the spinner
permanently stuck after a pull-to-refresh (major bug fix).
- Replace the empty Text('') with SizedBox.shrink() in
_buildFooterSliver when hasMore=true and isLoadingMore=false to
eliminate the 32px dead space (nit fix).
- Add three pagination widget tests to each screen: _loadMore appends
a second page, _loadMore is a no-op while already in-flight, and
pull-to-refresh while _loadMore is in-flight leaves _isLoadingMore=false
after _load completes (regression coverage for the major bug).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds a Completer<Media> field to _FakeApiClient so tests can hold a
download in-flight, and a new test that taps the download button twice
while the first call is pending to verify _pendingDownloads suppresses
the second API call.
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>
|
|
- Remove redundant multiSelectionEnabled: false from SegmentedButton
- DRY up theme toggle tests via optional themeNotifier param in _pumpSettingsScreen
- Add UI assertion to verify button reflects ThemeMode.dark after tap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Roll back ThemeNotifier state on SharedPreferences write failure so
in-memory and disk never diverge
- Fix stale comment: scaffoldBackgroundColor ← --bg-body (not background)
- Fix source attribution: theme.css not docs/theming.md
- Make _ThemeToggle const-constructible; remove incorrect ignore comment
- Inline _buildSegmentedButton into _ThemeToggle.build (was a trivial passthrough)
- Add defensive isNotEmpty comment on SegmentedButton.onSelectionChanged
- Hoist buildLightTheme()/buildDarkTheme() to module-level finals in main.dart
so ThemeData is built once at startup rather than on every rebuild
- Add _ThemeToggle tests: initial segment selection and segment tap dispatch
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>
|
|
Introduces ProgressQueue backed by SQLite for offline-capable progress
tracking, with ProgressQueueBase (LSP+DIP), databaseFactory injection
(DIP), and ProgressSyncClient narrow interface (ISP).
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>
|
|
Implements every previously-unimplemented method in DioPlayerApiClient:
shares (listSharesForMedia, listMyShares, revokeShare), public shared-media
endpoints (getSharedMediaPage, streamSharedMedia, getSharedThumbnail,
downloadSharedMedia), config (getConfig), sets (getSetCover, updateSetCover,
uploadToSet), media helpers (regenerateThumbnail, deleteMedia, restoreMedia),
progress batch (batchUpdateProgress), podcasts (listPodcasts, listEpisodes,
downloadEpisode, toggleEpisodeComplete), admin users/permissions/scanner
(listUsers, createUser, deleteUser, listPermissions, grantPermission,
revokePermission, triggerRescan, getScanProgress, listTrash), and API tokens
(listAPITokens, createAPIToken, revokeAPIToken).
Adds listAPITokens/createAPIToken/revokeAPIToken/batchUpdateProgress to the
abstract PlayerApiClient. Corrects listPermissions return type from
List<Map> to Map<String,dynamic> to match the server's single-object response.
Adds 18 new unit tests covering shares (listMyShares success/empty/401,
revokeShare success/404/401) and podcasts (listEpisodes success/pagination/
empty/401, toggleEpisodeComplete success/404/401). All 264 tests pass.
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>
|
|
Creates PlayerAudioHandler (BaseAudioHandler + SeekHandler) that wraps
just_audio's AudioPlayer and bridges it to the Android media session:
background foreground-service playback, lock-screen / notification
controls (play/pause/seek/skip ±15 s), audio focus, and Bluetooth
headset events all handled by audio_service.
Key design decisions:
- Handler registered once via AudioService.init in main() and injected
into ProviderScope via overrideWithValue (DIP: no global mutable var).
- Progress-sync timer stays in the screen so PlayerApiClient is never
imported by the handler (SRP boundary preserved).
- Seek bar onChanged routes through handler.seek() so the notification
position updates on slider drags (Law of Demeter fix).
- _initPlayer refactored into _buildAuthHeaders / _loadSource /
_resumeFromSavedPosition helpers (each ≤30 lines, SoC).
- Tests override audioHandlerProvider with _FakePlayerAudioHandler to
avoid platform-channel calls; all 221 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
MediaGridScreen (4b)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
MediaGridScreen (wa)
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>
|
|
Replaces the placeholder AudioPlayerScreen with a full implementation:
- Streams audio via just_audio AudioPlayer with Bearer token in headers
- Progress sync mirrors VideoPlayerScreen exactly: 5 s timer, isPlaying guard,
95 % finished threshold, _finishedEmitted guard, same dispose order
- UI: cover art placeholder, StreamBuilder-backed seek bar, play/pause,
skip ±15 s (fast_rewind/fast_forward icons), speed selector (0.5–2x)
- Resume from server-saved position via getMediaProgress on init
- Error state with retry button; all async continuations guard on mounted
- Widget tests cover: loading spinner, error view structure, AppBar title,
URL resolution; audio_session channel mocked to unblock headless tests
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>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Server: expose a public countUsers endpoint (GET /api/v1/auth/count)
so mobile clients can detect first-run (count=0) without a session.
Android: wire countUsers via DioPlayerApiClient, add firstRunProvider
(FutureProvider), update go_router redirect to drive /bootstrap vs /login
based on the count, rework LoginScreen to handle loading/error states, and
add widget tests for the new login screen and smoke-test updates.
Fix review issues: correct FutureProvider cache-lifetime comment in
first_run_provider.dart; add TestServer_CountUsers covering zero-users
and users-exist cases to handlers_test.go.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds the first-run admin-account setup screen (POST /api/v1/auth/bootstrap),
wires it into the go_router redirect guard, and extracts the pure
_dioErrorMessage helper to a top-level function to satisfy SRP.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Replace UnimplementedError stubs with concrete Dio calls for bootstrap, login,
logout, listSets, browseSet, listMedia, getMedia, streamMedia, downloadMedia,
getThumbnail, healthz, and readyz. Wire DioPlayerApiClient into the Riverpod
provider. Add http_mock_adapter dev dependency and 25 unit tests covering
success + error paths for all implemented methods.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Introduces dio_client.dart with _AuthInterceptor (guards Bearer token
injection with containsKey so callers can override Authorization) and
_UnauthorizedInterceptor (private fields, 401 → login redirect).
PlayerApiClient is refactored to accept a pre-configured Dio instance
instead of raw credentials; adds dio + flutter_secure_storage deps.
Fixes review issues: Auth header comment/behavior corrected, private
fields on _UnauthorizedInterceptor, unused import removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Remove const from PlayerApiClient constructor (Uri is not const-constructable)
- Replace .cast<String>() with .whereType<String>().toList() in Media.fromJson
to silently drop non-string and null tag elements instead of throwing TypeError
- Add regression test: tags [1, 'valid', null] deserialized as ['valid']
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
Wrap DateTime.parse in try/catch so a malformed or non-ISO-8601 date
string from the server degrades to null instead of throwing a
FormatException that crashes model deserialization across Media,
MediaSet, User, PodcastFeed, PodcastEpisode, PlaybackHint, Share, and
Note.
Add player-android/test/json_helpers_test.dart with 11 tests covering
null, empty, non-string, valid ISO, and malformed inputs (regression
guard for the original crash).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
Adds HomeScreen ('/') and NowPlayingScreen ('/now-playing') as the first two
named routes in PlayerAndroidApp, replacing the anonymous home widget. Widget
smoke tests verify that the app starts on the library screen, navigates to the
now-playing screen on button tap, and back-navigates correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Add fromJson/toJson round-trip tests for all 9 Dart models (Media,
MediaSet, User, Tag, Note, Share, PodcastFeed, PodcastEpisode,
PlaybackHint) and 3 PlayerApiClient constructor tests. Each model
group covers a fully-populated round-trip (including int-to-double
cast verification and ISO-8601 DateTime value comparison) and an
exhaustive defaults-from-empty-map test. All 21 tests run via
'flutter test' with no emulator.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|