From 69fadc220d68cbe84cbceef66900aa62651a8fac Mon Sep 17 00:00:00 2001
From: edde746 <86283021+edde746@users.noreply.github.com>
Date: Mon, 10 Aug 2026 17:14:53 +0200
Subject: [PATCH] chore: clean up code comments
---
.github/workflows/build.yml | 2 -
.gitignore | 4 +-
.../regression_flows/03_tv_library_focus.yaml | 9 +-
.../regression_flows/04_tv_player_keys.yaml | 22 ++--
.../05_tv_next_episode_back.yaml | 17 +--
.../06_playback_recovery.yaml | 6 +-
.../07_sheet_back_dismiss.yaml | 27 +----
...08_track_choice_survives_pending_pass.yaml | 18 +--
.../09_language_picker_locales.yaml | 17 +--
.../10_tv_settings_navigation.yaml | 20 +--
.maestro/subflows/ensure_onboarded.yaml | 18 +--
.maestro/subflows/onboard_jellyfin.yaml | 6 +-
.../subflows/onboard_jellyfin_tv_device.yaml | 14 +--
.maestro/subflows/open_codec_sample.yaml | 6 +-
.maestro/subflows/open_settings.yaml | 8 +-
analysis_options.yaml | 9 +-
android/app/build.gradle.kts | 2 -
android/app/src/main/AndroidManifest.xml | 13 --
.../kotlin/com/edde746/plezy/MainActivity.kt | 1 -
.../plezy/exoplayer/TrueHdCarrierSink.kt | 2 -
.../com/edde746/plezy/mpv/MpvPlayerCore.kt | 3 -
.../com/edde746/plezy/mpv/MpvPlayerPlugin.kt | 4 -
.../exoplayer/BufferingStallPolicyTest.kt | 7 --
.../exoplayer/DvBitstreamSanitizerTest.kt | 4 -
.../plezy/exoplayer/EndOfStreamPolicyTest.kt | 2 -
.../plezy/exoplayer/LoadControlPolicyTest.kt | 4 -
.../exoplayer/RawPositionAudioOutputTest.kt | 4 -
.../plezy/exoplayer/ResumeStallPolicyTest.kt | 2 -
.../plezy/exoplayer/TrueHdCarrierSinkTest.kt | 4 -
android/fastlane/Fastfile | 3 -
assets/rating_icons/rt_fresh.svg | 1 -
assets/rating_icons/rt_spilled.svg | 1 -
ios/Podfile | 1 -
ios/Runner/AppDelegate.swift | 6 +-
ios/Runner/MpvPlayer/MpvPipController.swift | 7 --
ios/Runner/MpvPlayer/MpvPlayerPlugin.swift | 4 -
ios/fastlane/Fastfile | 3 -
lib/database/app_database.dart | 1 -
lib/focus/dpad_reorder_mixin.dart | 2 -
lib/focus/focus_memory_tracker.dart | 3 -
lib/focus/focusable_button.dart | 1 -
lib/focus/focusable_wrapper.dart | 25 +---
lib/focus/input_mode_tracker.dart | 2 -
lib/providers/download_provider.dart | 13 --
lib/providers/libraries_provider.dart | 3 -
lib/providers/offline_mode_provider.dart | 1 -
lib/providers/offline_watch_provider.dart | 6 -
lib/screens/discover_screen.dart | 11 --
lib/screens/downloads/downloads_screen.dart | 7 --
lib/screens/hub_detail_screen.dart | 6 -
lib/screens/libraries/alpha_jump_helper.dart | 3 -
.../libraries/content_state_builder.dart | 4 -
lib/screens/libraries/libraries_screen.dart | 21 ----
lib/screens/libraries/sort_bottom_sheet.dart | 1 -
lib/screens/playlist/playlist_item_card.dart | 17 +--
lib/screens/settings/about_screen.dart | 2 -
lib/screens/settings/licenses_screen.dart | 2 -
lib/services/ambient_lighting_service.dart | 2 -
lib/services/discord_rpc_service.dart | 8 --
lib/services/gamepad_service.dart | 5 -
lib/services/keyboard_shortcuts_service.dart | 5 -
lib/utils/content_utils.dart | 2 -
lib/widgets/bottom_sheet_header.dart | 1 -
lib/widgets/download_tree_view.dart | 33 -----
lib/widgets/hub_section.dart | 1 -
lib/widgets/library_management_sheet.dart | 4 -
lib/widgets/media_card.dart | 8 --
lib/widgets/media_context_menu.dart | 2 +-
.../desktop_video_controls.dart | 11 --
.../video_controls/mobile_video_controls.dart | 2 -
.../video_controls/parts/track_controls.dart | 2 -
.../sheets/video_settings_sheet.dart | 7 --
.../video_controls/video_control_button.dart | 7 +-
.../performance_stats.dart | 14 +--
.../widgets/track_chapter_controls.dart | 11 --
linux/CMakeLists.txt | 5 -
linux/packaging/after-install.sh | 3 -
linux/packaging/after-remove.sh | 2 -
linux/packaging/build-packages.py | 5 -
linux/runner/CMakeLists.txt | 18 +--
macos/Runner/Configs/AppInfo.xcconfig | 8 --
macos/Runner/MainFlutterWindow.swift | 8 +-
macos/Runner/MpvPlayer/MpvPipController.swift | 5 -
macos/Runner/MpvPlayer/MpvPlayerPlugin.swift | 18 +--
macos/Runner/WindowDelegate.swift | 4 -
macos/Runner/WindowUtilsPlugin.swift | 13 +-
.../fluttercavalry/saf_util/SafUtilPlugin.kt | 13 +-
pubspec.yaml | 27 ++---
scripts/check_build_workflow.py | 15 +--
scripts/check_hardcoded_strings.py | 22 +---
scripts/check_linux_package_deps.py | 82 +++----------
scripts/check_shrinker_rules.py | 12 +-
scripts/check_windows_installer.py | 11 +-
scripts/check_windows_msix.py | 43 ++-----
scripts/ci_checks.sh | 18 +--
scripts/ci_guard_checks.sh | 15 +--
scripts/generate_android_icons.sh | 26 +---
scripts/generate_windows_msix_assets.ps1 | 26 +---
scripts/run_maestro.py | 10 +-
scripts/run_maestro_ci.py | 25 +---
scripts/run_tests.sh | 40 ++----
server/artifact_store.go | 41 ++-----
server/main.go | 107 ++++++----------
server/main_test.go | 114 +++++-------------
server/oauth.go | 67 ++++------
server/oauth_test.go | 31 ++---
server/rate_limit.go | 18 +--
slang.yaml | 5 +-
.../connection/connection_bootstrap_test.dart | 1 -
test/connection/connection_models_test.dart | 7 +-
test/connection/connection_registry_test.dart | 7 --
test/database/app_database_test.dart | 27 -----
test/database/download_operations_test.dart | 27 +----
test/focus/key_event_utils_test.dart | 3 -
test/media/media_playlist_test.dart | 13 +-
test/mixins/paginated_item_loader_test.dart | 7 --
test/mixins/tab_navigation_mixin_test.dart | 2 -
test/mixins/watch_state_aware_test.dart | 1 -
...ayer_android_subtitle_visibility_test.dart | 1 -
test/mpv/player_error_http_status_test.dart | 1 -
.../hidden_libraries_provider_test.dart | 3 -
.../providers/multi_server_provider_test.dart | 4 -
test/providers/shader_provider_test.dart | 3 -
test/providers/theme_provider_test.dart | 3 -
.../music/album_detail_screen_test.dart | 2 -
test/screens/music/queue_sheet_test.dart | 1 -
.../base_shared_preferences_service_test.dart | 11 +-
test/services/bif_thumbnail_service_test.dart | 34 +-----
.../data_aggregation_bridge_test.dart | 7 +-
.../download_storage_service_test.dart | 36 ------
.../episode_navigation_service_test.dart | 4 -
.../jellyfin_client_failures_test.dart | 9 +-
test/services/jellyfin_client_urls_test.dart | 7 +-
test/services/jellyfin_mappers_test.dart | 2 -
test/services/jellyfin_media_info_test.dart | 2 -
test/services/jellyfin_music_mapper_test.dart | 1 -
.../services/local_playback_history_test.dart | 1 -
test/services/multi_server_manager_test.dart | 29 -----
.../offline_watch_sync_service_test.dart | 50 +-------
test/services/play_queue_launcher_test.dart | 8 --
.../playback_progress_tracker_test.dart | 33 +----
test/services/plex_api_cache_test.dart | 20 ---
test/services/plex_mappers_test.dart | 2 -
test/services/sleep_timer_service_test.dart | 32 -----
test/services/storage_service_test.dart | 44 -------
test/services/track_manager_test.dart | 32 -----
.../track_selection_service_test.dart | 75 +-----------
tvos/Runner/AppDelegate.swift | 8 +-
tvos/scripts/set_tvos_target_pods.sh | 1 -
tvos/scripts/set_tvos_target_runner.sh | 1 -
website/src/app.d.ts | 7 --
.../src/lib/components/DownloadButtons.svelte | 3 -
website/src/lib/components/Features.svelte | 9 --
website/src/lib/components/Screenshots.svelte | 6 +-
.../src/lib/components/SectionHeader.svelte | 4 +-
website/src/lib/content/downloads.ts | 16 +--
website/src/lib/index.ts | 1 -
.../src/lib/server/homepage_store_metadata.ts | 4 +-
website/src/routes/+page.server.ts | 3 +-
website/src/routes/layout.css | 9 +-
website/svelte.config.js | 3 -
website/tsconfig.json | 6 +-
windows/CMakeLists.txt | 8 --
windows/build-installer.ps1 | 10 --
windows/runner/CMakeLists.txt | 14 ---
windows/runner/flutter_window.cpp | 14 +--
windows/runner/flutter_window.h | 6 -
windows/runner/main.cpp | 4 +-
windows/runner/mpv/display_mode_manager.cpp | 3 -
windows/runner/mpv/mpv_plugin.cpp | 3 -
170 files changed, 324 insertions(+), 1765 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ebf19a46..d423681d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -271,13 +271,11 @@ jobs:
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
- # Create temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/build.keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
- # Import certificate to keychain
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
echo "$MACOS_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
security import $CERTIFICATE_PATH -k $KEYCHAIN_PATH -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
diff --git a/.gitignore b/.gitignore
index 99f47ebb..9b6ba5b0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,9 +18,7 @@ migrate_working_dir/
*.iws
.idea/
-# The .vscode folder contains launch configuration and tasks you configure in
-# VS Code which you may wish to be included in version control, so this line
-# is commented out by default.
+# Keep .vscode/ available for contributors' local configuration.
#.vscode/
# Flutter/Dart/Pub related
diff --git a/.maestro/regression_flows/03_tv_library_focus.yaml b/.maestro/regression_flows/03_tv_library_focus.yaml
index 4951eb75..15c0b4c5 100644
--- a/.maestro/regression_flows/03_tv_library_focus.yaml
+++ b/.maestro/regression_flows/03_tv_library_focus.yaml
@@ -22,8 +22,8 @@ tags:
- pressKey: "Remote Dpad Up"
- pressKey: "Remote Dpad Right"
- pressKey: "Remote Dpad Right"
-# The grid row must be matchable before the sort sheet opens, otherwise the
-# occlusion assertion below would pass for the wrong reason.
+# Wait for the row before opening the sheet so occlusion is tested against the
+# grid, not an absent or stale match.
- extendedWaitUntil:
visible: "(?s).*Alpha Archive.*"
timeout: 15000
@@ -31,9 +31,8 @@ tags:
- extendedWaitUntil:
visible: "Title"
timeout: 10000
-# The host renders the grid, the barrier, and the sheet in one Stack and never
-# unmounts the grid, so before 8e1904dd wrapped the barrier in BlockSemantics
-# an occluded row still read as visible to accessibility and to Maestro.
+# The grid remains mounted under the Stack; BlockSemantics is required for
+# occluded rows to disappear from Maestro's accessibility tree.
- assertNotVisible: "(?s).*Alpha Archive.*"
- pressKey: "Remote Dpad Center"
- pressKey: "Remote Dpad Left"
diff --git a/.maestro/regression_flows/04_tv_player_keys.yaml b/.maestro/regression_flows/04_tv_player_keys.yaml
index c9335d46..f6a99b17 100644
--- a/.maestro/regression_flows/04_tv_player_keys.yaml
+++ b/.maestro/regression_flows/04_tv_player_keys.yaml
@@ -16,13 +16,9 @@ tags:
visible: "(?s)^(Play|Resume).*$"
commands:
- pressKey: "Remote Dpad Center"
-# The TV player opens with its chrome down so the OSD and timebar never sit
-# over the opening seconds of the picture (#1765), so the Pause button can no
-# longer stand in for "the player has finished loading". Wait for the detail
-# screen to go first: the labelled spinner is necessarily up the moment the
-# player route owns the frame, and it only clears once playback reports its
-# first frame or gives up, so this can never advance before the media opens.
-# A give-up lands on the transport assertions below, which is where it belongs.
+# TV playback starts with chrome hidden (#1765), so wait for its loading label
+# rather than treating the Pause button as readiness. A failed open reaches the
+# transport assertions below.
- extendedWaitUntil:
notVisible: "Overview"
timeout: 30000
@@ -30,18 +26,14 @@ tags:
notVisible: "Loading video"
timeout: 30000
- assertNotVisible: "(?s)^(Play|Pause)$"
-# Hardware transport keys drive playback without raising the chrome (#1676):
-# the player answers with a centred disc (pause) or a side readout (seek), never the
-# full chrome, so subtitles stay readable.
-# Pause first — it also freezes the clip, so the seek assertions below cannot
-# race the end of a short fixture item.
+# Transport keys keep chrome hidden (#1676); pause first so seeks cannot race
+# the end of this short fixture.
- pressKey: "Remote Media Play Pause"
- extendedWaitUntil:
visible: "Paused"
timeout: 10000
- assertNotVisible: "(?s)^Play$"
-# D-pad seeking reports through the skip badge, never the scrub bar, and
-# consecutive presses in one direction stack into a running total.
+# D-pad seeks use the cumulative skip badge, not the scrub bar.
- pressKey: "Remote Dpad Right"
- extendedWaitUntil:
visible: "(?s)^Seek forward 10 seconds$"
@@ -56,7 +48,7 @@ tags:
visible: "Playing"
timeout: 10000
- assertNotVisible: "(?s)^Pause$"
-# Select stays the deliberate way to bring the chrome back.
+# Select is the explicit way to restore the chrome.
- pressKey: "Remote Dpad Center"
- extendedWaitUntil:
visible: "(?s)^(Play|Pause)$"
diff --git a/.maestro/regression_flows/05_tv_next_episode_back.yaml b/.maestro/regression_flows/05_tv_next_episode_back.yaml
index 1069c8c5..8ae56194 100644
--- a/.maestro/regression_flows/05_tv_next_episode_back.yaml
+++ b/.maestro/regression_flows/05_tv_next_episode_back.yaml
@@ -7,10 +7,8 @@ tags:
- playback
---
- runFlow: ../subflows/onboard_jellyfin_tv.yaml
-# Reach Search with the D-pad, the way 10_tv_settings_navigation.yaml does. A
-# tap here flips InputModeTracker into pointer mode, which collapses the rail
-# to icons and leaves no "Search" label to hit — and a percentage coordinate
-# does not survive the jump from a phone in forced TV mode to a 4K television.
+# Reach Search by D-pad: taps switch to pointer mode and hide the TV rail label,
+# while percentage coordinates are not portable across TV sizes.
- pressKey: "Remote Dpad Left"
- extendedWaitUntil:
visible: "Search"
@@ -38,11 +36,8 @@ tags:
- waitForAnimationToEnd:
timeout: 5000
- tapOn: "(?s)^Play S1E1$"
-# The TV player opens with its chrome down (#1765), so the Pause button can no
-# longer stand in for "the player has finished loading". Wait for the detail
-# screen to go first: the labelled spinner is necessarily up the moment the
-# player route owns the frame, and it only clears once playback reports its
-# first frame or gives up, so this can never advance before the media opens.
+# The player starts with chrome hidden (#1765); wait for its loading label
+# rather than using Pause as readiness.
- extendedWaitUntil:
notVisible: "(?s)^Play S1E1$"
timeout: 30000
@@ -51,9 +46,7 @@ tags:
timeout: 30000
- pressKey: "Remote Media Fast Forward"
- pressKey: "Remote Media Fast Forward"
-# Gate on the prompt's own Cancel action, the label the iOS branch below taps.
-# "Next Episode" is the credits skip button at this point in the episode, so
-# waiting on it would let Back fire before the prompt ever opened.
+# Wait for Cancel, not Next Episode (the credits-skip action), before pressing Back.
- extendedWaitUntil:
visible: "(?s)^Cancel$"
timeout: 20000
diff --git a/.maestro/regression_flows/06_playback_recovery.yaml b/.maestro/regression_flows/06_playback_recovery.yaml
index 8cbc5fcb..cbcb3ba7 100644
--- a/.maestro/regression_flows/06_playback_recovery.yaml
+++ b/.maestro/regression_flows/06_playback_recovery.yaml
@@ -8,10 +8,8 @@ tags:
---
- runFlow: ../subflows/onboard_jellyfin.yaml
- tapOn: "(?s)^Libraries.*"
-# Every seeded alphabet title carries the same dateadded, so which of them the
-# "Recently Added" rail returns is not stable across scans. This flow only
-# needs some playable movie, so accept any of them rather than pinning one and
-# failing whenever the tie-break lands elsewhere.
+# All seeded titles share dateadded, so accept any playable Recently Added movie
+# instead of relying on an unstable tie-break.
- extendedWaitUntil:
visible: "(?s).*(?:Alpha Archive|Bravo Beacon|Charlie Circuit|Delta Drive|Echo Engine|Foxtrot Frame|Gamma Garden|Hotel Horizon|India Index|Juliet Junction|Kilo Key|Lima Loop|Mike Matrix|November Node|Oscar Orbit|Papa Pipeline|Quebec Queue|Romeo Relay|Sierra Signal|Tango Track|Uniform Update|Victor View|Whiskey Widget|Xray XML|Yankee Yield|Zulu Zone).*, movie, .*"
timeout: 15000
diff --git a/.maestro/regression_flows/07_sheet_back_dismiss.yaml b/.maestro/regression_flows/07_sheet_back_dismiss.yaml
index d9c09782..4612a881 100644
--- a/.maestro/regression_flows/07_sheet_back_dismiss.yaml
+++ b/.maestro/regression_flows/07_sheet_back_dismiss.yaml
@@ -7,20 +7,9 @@ tags:
- settings
- sheets
---
-# Guards 8e1904dd and c48cbf70 together.
-#
-# On a phone, Settings is a pushed route. Before c48cbf70 it carried no
-# OverlaySheetHost, so Manage Libraries fell back to showModalBottomSheet and a
-# single Back tore down the sheet *and* Settings. Before 8e1904dd the host
-# answered the platform pop with a TV-only dedup marker that could already be
-# spent, stranding the sheet open with no way out. Either defect fails the
-# "Manage Libraries is still there after one Back" assertion below.
-#
-# The same commit blocks semantics behind the barrier, so the occlusion
-# assertions pin that an open sheet actually hides the rows underneath.
-# Pristine state on purpose. The TV regressions in the same CI group leave
-# "Force TV mode" enabled, and this flow asserts the handheld Settings route,
-# so it must not inherit whichever layout the previous flow left behind.
+# Guards 8e1904dd and c48cbf70: phone Settings must host the sheet so one Back
+# dismisses only the sheet, while BlockSemantics hides rows behind the barrier.
+# Start clean because preceding TV flows may leave Force TV mode enabled.
- runFlow: ../subflows/onboard_jellyfin.yaml
- runFlow: ../subflows/open_settings.yaml
- assertVisible: "(?s)^Services.*"
@@ -29,10 +18,8 @@ tags:
- extendedWaitUntil:
visible: "Maestro Movies"
timeout: 15000
-# The barrier swallows every pointer event, so the rows behind it must leave
-# the semantics tree too. Without BlockSemantics an occluded row still reads as
-# visible and a tap on it lands on the barrier instead. "Manage Libraries" is
-# excluded on purpose: it is also the sheet's own title.
+# The barrier removes covered rows from semantics; the sheet title remains
+# visible because it belongs to the sheet.
- assertNotVisible: "(?s)^Services.*"
- assertNotVisible: "(?s)^Video Playback.*"
- assertNotVisible: "(?s)^Appearance.*"
@@ -41,15 +28,13 @@ tags:
- waitForAnimationToEnd:
timeout: 3000
- assertNotVisible: "Maestro Movies"
-# One Back closed only the sheet: Settings is still the current route.
- extendedWaitUntil:
visible: "(?s)^Manage Libraries.*"
timeout: 10000
- assertVisible: "(?s)^Services.*"
- assertVisible: "(?s)^Video Playback.*"
- assertNotVisible: "Discover"
-# Reopening proves the host did not strand the one-shot dedup marker: a second
-# dismissal has to work exactly like the first.
+# Reopening verifies the one-shot pop deduplication marker was not stranded.
- tapOn: "(?s)^Manage Libraries.*"
- extendedWaitUntil:
visible: "Maestro Movies"
diff --git a/.maestro/regression_flows/08_track_choice_survives_pending_pass.yaml b/.maestro/regression_flows/08_track_choice_survives_pending_pass.yaml
index 79ce2958..f5820fc2 100644
--- a/.maestro/regression_flows/08_track_choice_survives_pending_pass.yaml
+++ b/.maestro/regression_flows/08_track_choice_survives_pending_pass.yaml
@@ -7,20 +7,10 @@ tags:
- playback
- subtitles
---
-# Guards 468d6804. When a source advertises tracks the native list has not
-# produced yet, TrackManager arms an automatic selection pass with a 5s first
-# attempt and a 25s deadline. A user pick made inside that window used to only
-# persist the preference, leaving the pass armed, so the choice snapped back to
-# the server/profile preferred track up to ~30s later. The pick now retires the
-# pending pass, and the wait below is long enough to outlive both timers.
-#
-# Fixture: Codec H264 EAC3 Multisub advertises three E-AC-3 audio streams
-# (Hindi and Japanese flagged default, English not) and 36 SRT subtitle
-# streams, so both picks below are genuine changes away from the automatic one.
-# Pristine state on purpose, for two reasons: the TV regressions in the same
-# CI group leave "Force TV mode" enabled, and a remembered track selection
-# from an earlier run would pre-select the very rows this flow picks, making
-# every assertion below vacuous.
+# Guards 468d6804: a user pick must retire TrackManager's pending automatic
+# selection pass, or the server preference can overwrite it up to 30s later.
+# The codec fixture supplies non-default audio and subtitle choices, and clean
+# state prevents remembered selections from making the assertions vacuous.
- runFlow: ../subflows/onboard_jellyfin.yaml
- runFlow:
file: ../subflows/open_codec_sample.yaml
diff --git a/.maestro/regression_flows/09_language_picker_locales.yaml b/.maestro/regression_flows/09_language_picker_locales.yaml
index ff2fd96b..0db1d4bf 100644
--- a/.maestro/regression_flows/09_language_picker_locales.yaml
+++ b/.maestro/regression_flows/09_language_picker_locales.yaml
@@ -7,16 +7,9 @@ tags:
- settings
- i18n
---
-# Guards 7677d159 and 100d7729. AppLocale is switched over exhaustively in
-# appearance_settings_screen.dart, so a missing arm is a compile error, but a
-# locale whose JSON never reached the generator ships a picker entry that falls
-# back to English. Selecting Turkish and reading root-level navigation labels
-# proves the generated locale is wired end to end.
-#
-# This flow mutates a persisted pref, and the app language decides every
-# selector after the tap, so it starts from a cleared install rather than
-# ensure_onboarded: a run that dies mid-flow must not leave the next flow
-# hunting for English strings in a Turkish tree.
+# Guards 7677d159 and 100d7729: selecting Turkish and reading root navigation
+# labels proves the generated locale is wired end to end. Start clean because
+# the language preference persists and affects every later selector.
- runFlow: ../subflows/onboard_jellyfin.yaml
- runFlow: ../subflows/open_settings.yaml
- tapOn: "(?s)^Appearance.*"
@@ -54,9 +47,7 @@ tags:
- assertVisible: "Keşfet"
- assertNotVisible: "(?s)^Libraries.*"
- assertNotVisible: "Discover"
-# Walk back through the Turkish tree and restore English. The pref outlives a
-# plain launchApp, so leaving the device in Turkish would break every later
-# flow in the suite.
+# Restore English before leaving the device; the locale preference outlives launchApp.
- repeat:
times: 3
while:
diff --git a/.maestro/regression_flows/10_tv_settings_navigation.yaml b/.maestro/regression_flows/10_tv_settings_navigation.yaml
index 1b1f532d..5c450de3 100644
--- a/.maestro/regression_flows/10_tv_settings_navigation.yaml
+++ b/.maestro/regression_flows/10_tv_settings_navigation.yaml
@@ -7,21 +7,11 @@ tags:
- tv
- settings
---
-# Guards ef310459 and the Apple-only gates touched by 6c14049e, and pins TV
-# settings navigation generally.
-#
-# It deliberately does NOT guard the 15b54e2e density change. That commit took
-# every settings row from ~80dp to ~61dp, but the rows below the fold stay in
-# the semantics tree either way, so this flow passes identically on both sides
-# of it — measured, not assumed. A real guard would need a pixel `height`
-# assertion, and Maestro has no DPR-relative form, so pinning one here would
-# only hold for the box it was written on. Row geometry belongs to
-# test/widgets/setting_tile_test.dart.
-#
-# Every step uses D-pad keys, never taps. InputModeTracker flips to pointer
-# mode on the first touch event and the TV number spinner only builds in
-# keyboard mode, so a single tapOn anywhere above would silently swap the
-# spinner for a plain text field.
+# Guards ef310459 and the Apple-only gates touched by 6c14049e.
+# This flow intentionally does not pin 15b54e2e's row density: semantics stay
+# stable across that change, and Maestro has no DPR-relative height assertion.
+# Use D-pad only; a tap switches InputModeTracker to pointer mode and removes
+# the TV number spinner.
- runFlow: ../subflows/onboard_jellyfin_tv_device.yaml
- pressKey: "Remote Dpad Left"
- extendedWaitUntil:
diff --git a/.maestro/subflows/ensure_onboarded.yaml b/.maestro/subflows/ensure_onboarded.yaml
index 5eb9c0c8..3b210a65 100644
--- a/.maestro/subflows/ensure_onboarded.yaml
+++ b/.maestro/subflows/ensure_onboarded.yaml
@@ -1,20 +1,10 @@
appId: com.edde746.plezy
---
-# Reach a signed-in Home without paying for onboarding that already happened.
-#
-# `onboard_jellyfin.yaml` clears app state and retypes the server URL and
-# credentials, which measures ~59s per flow on a Pixel 7 and dominates every
-# flow that only needs "signed in, on Home". A plain `launchApp` keeps stored
-# data and cold-starts straight back to Home, which measures ~16s.
-#
-# The guard keeps each flow runnable on its own: when no session is stored --
-# a fresh install, or a run after the logout flow -- "Discover" is absent and
-# this falls through to the real onboarding. Flows that must prove onboarding
-# itself, or that need pristine state, keep calling onboard_jellyfin.yaml.
+# Reach a signed-in Home without repeating onboarding. A plain launch preserves
+# stored state (~16s versus ~59s for retyping credentials); fall back to full
+# onboarding when no session exists so each flow remains runnable alone.
- launchApp
-# Settle on a known screen first. Branching straight off `launchApp` races the
-# splash: "Discover" has not rendered yet, the guard reads it as absent, and
-# every flow pays for a full re-onboarding it did not need.
+# Wait for the splash to resolve before deciding whether onboarding is needed.
- extendedWaitUntil:
visible: "(?s)^(?:Discover|Sign in with Plex|Connect to Jellyfin|Wait).*"
timeout: 30000
diff --git a/.maestro/subflows/onboard_jellyfin.yaml b/.maestro/subflows/onboard_jellyfin.yaml
index 62810d19..cd87bbd2 100644
--- a/.maestro/subflows/onboard_jellyfin.yaml
+++ b/.maestro/subflows/onboard_jellyfin.yaml
@@ -10,10 +10,8 @@ appId: com.edde746.plezy
- extendedWaitUntil:
visible: "(?s)^(?:Connect to Jellyfin|Wait).*"
timeout: 30000
- # A miss on `tapOn ... optional: true` still runs Maestro's full element
- # search before giving up, which measured 3s here and 7.8s for "Sign in"
- # on every onboarding. These taps normally find nothing, so gate them on
- # a single visibility check instead.
+# Optional taps still pay Maestro's full search cost when they miss; guard them
+# with one visibility check instead.
- runFlow:
when:
visible: "Wait"
diff --git a/.maestro/subflows/onboard_jellyfin_tv_device.yaml b/.maestro/subflows/onboard_jellyfin_tv_device.yaml
index b61da160..b4317cb9 100644
--- a/.maestro/subflows/onboard_jellyfin_tv_device.yaml
+++ b/.maestro/subflows/onboard_jellyfin_tv_device.yaml
@@ -1,12 +1,7 @@
appId: com.edde746.plezy
---
-# Onboarding for a device that already reports itself as a TV.
-#
-# `onboard_jellyfin_tv.yaml` exists for the phone emulator CI runs on: it
-# onboards through the handheld layout and then walks Settings to flip
-# "Force TV mode". A real Android TV box takes the TV layout from the first
-# frame, so that walk has no handheld chrome to tap and the toggle would only
-# re-assert what PlatformDetector already reports.
+# Onboard a device that reports itself as a TV; unlike the phone flow, it starts
+# in TV layout and needs no Force TV mode walk.
- retry:
maxRetries: 1
commands:
@@ -32,9 +27,8 @@ appId: com.edde746.plezy
- tapOn: "(?s)Server URLs.*"
- inputText: ${JELLYFIN_URL}
- tapOn: "Find server"
-# A TV-sized layout offers Quick Connect first because typing on a remote is
-# slow. The code path needs a second device to approve, so fall back to the
-# username form the phone flow already uses.
+# Quick Connect is awkward on a remote and needs another device, so use the
+# username form as fallback.
- extendedWaitUntil:
visible: "(?s)^(?:Username|Cancel)$"
timeout: 30000
diff --git a/.maestro/subflows/open_codec_sample.yaml b/.maestro/subflows/open_codec_sample.yaml
index 7bae2124..fafd1e49 100644
--- a/.maestro/subflows/open_codec_sample.yaml
+++ b/.maestro/subflows/open_codec_sample.yaml
@@ -17,10 +17,8 @@ appId: com.edde746.plezy
platform: Android
commands:
- hideKeyboard
-# A card announces its watch state three ways, and the codec samples keep a
-# resume position on the server once any earlier flow has played them, so the
-# partial form has to be accepted for this subflow to be re-runnable against a
-# fixture container that is not thrown away between suites.
+# Codec cards retain watch state and resume position between flows, so accept
+# any valid watch-state label to keep this subflow rerunnable.
- extendedWaitUntil:
visible: "(?s)^${SAMPLE_TITLE}, movie, (?:watched|unwatched|[0-9]+ percent watched)$"
timeout: 15000
diff --git a/.maestro/subflows/open_settings.yaml b/.maestro/subflows/open_settings.yaml
index 00bc0449..249ce78a 100644
--- a/.maestro/subflows/open_settings.yaml
+++ b/.maestro/subflows/open_settings.yaml
@@ -1,11 +1,7 @@
appId: com.edde746.plezy
---
-# Reach Settings from a signed-in Home on a phone layout.
-#
-# The profile menu is itself an overlay sheet on the MainScreen host, so a tap
-# issued while the previous route is still settling opens nothing. The guarded
-# repeat re-opens the menu instead of failing the flow, matching the pattern
-# already used by flows/07_profiles_settings.yaml.
+# Reach phone Settings from signed-in Home. The profile menu is an overlay
+# sheet; retry while the previous route settles.
- extendedWaitUntil:
visible: "Discover"
timeout: 30000
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 57755a26..c6b3ff7d 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -15,8 +15,7 @@ linter:
prefer_final_in_for_each: true
avoid_print: true
-# Do not register DCL as an analyzer plugin: its analyzer 10 integration crashes
-# on Linux. CI invokes the supported check-unused commands explicitly.
+# DCL's analyzer 10 plugin crashes on Linux; CI runs check-unused commands directly.
dart_code_linter:
rules-exclude:
- "test/**"
@@ -27,7 +26,7 @@ dart_code_linter:
- package:dart_code_linter/presets/recommended.yaml
rules:
- # --- Flutter rules (on top of recommended) ---
+ # Flutter-specific rules.
- avoid-border-all
- avoid-shrink-wrap-in-lists
- avoid-expanded-as-spacer
@@ -37,7 +36,7 @@ dart_code_linter:
- prefer-define-hero-tag
- use-setstate-synchronously
- # --- Additional useful Dart rules ---
+ # Additional Dart rules.
- avoid-cascade-after-if-null
- avoid-collection-methods-with-unrelated-types
- avoid-unnecessary-type-assertions
@@ -48,7 +47,7 @@ dart_code_linter:
- prefer-enums-by-name
- prefer-commenting-analyzer-ignores
- # --- Disable noisy rules from recommended preset ---
+ # Disabled noisy rules.
- no-magic-number: false
- avoid-dynamic: false
- format-comment: false
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index c7ba7ea5..5b33b40f 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -336,8 +336,6 @@ android {
defaultConfig {
applicationId = "com.edde746.plezy"
- // You can update the following values to match your application needs.
- // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = 25 // Fire OS 6.x (API 25); overrides libmpv-android's minSdk=26
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 0f1db247..22ab1946 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,17 +9,14 @@
-
-
-
@@ -30,15 +27,11 @@
-
-
-
-
-
-
@@ -88,7 +76,6 @@
-
-