feat: add available platforms badge with url launcher and improve grid layouts - #291
feat: add available platforms badge with url launcher and improve grid layouts#291Ziedelth wants to merge 5 commits into
Conversation
Ziedelth
commented
Aug 19, 2026
- Add url field to SourceModel for platform redirection
- Create AvailablePlatformsBadge allowing direct launch or popup selection of streaming platforms
- Modularize platform widgets into PlatformsStack and PlatformsBadge in lib/core/widgets/platforms/
- Switch grid layouts in CatalogView, GroupedEpisodesView, and WeeklyView to AlignedGridView.count
- Add 'availableOn' localization string and adjust theme text style
…d layouts - Add url field to SourceModel for platform redirection - Create AvailablePlatformsBadge allowing direct launch or popup selection of streaming platforms - Modularize platform widgets into PlatformsStack and PlatformsBadge in lib/core/widgets/platforms/ - Switch grid layouts in CatalogView, GroupedEpisodesView, and WeeklyView to AlignedGridView.count - Add 'availableOn' localization string and adjust theme text style
Ziedelth
left a comment
There was a problem hiding this comment.
Review Hermes
🔴 Changes requested: 3 guideline violations confirmed.
available_platforms_badge.dart:36: untrusted API URL is launched without validation.available_platforms_badge.dart:57: launch policy and retry behavior live in the widget.catalog_view.dart:88: widget hardcodesColors.black.
dart format --output=none --set-exit-if-changed lib and dart analyze lib pass.
⚠️ Points incertains / à clarifier
available_platforms_badge.dart:91:singleWherethrows if one platform has multiple source URLs. The behavior is confirmed for duplicate matches, but the API contract does not establish whether duplicates are possible. Prefer grouping/selecting sources without assuming one URL per platform.available_platforms_badge.dart:80: an emptysourcesiterable reachesshowMenu(items: []), whose implementation assertsitems.isNotEmpty. The crash is reproduced, but it is unclear whether the API guarantees a non-empty list. Hide/disable the badge or guard the empty case.
|
|
||
| for (final mode in modes) { | ||
| try { | ||
| if (await launchUrl(.parse(url), mode: mode)) { |
There was a problem hiding this comment.
🔴 SourceModel.url comes from the API, but this passes it directly through Uri.parse to the platform launcher. Uri.parse can throw for malformed input and this code also accepts arbitrary schemes; the handler only catches PlatformException. This violates SECURITY.md's requirement to validate external input and fail safely. Parse with Uri.tryParse, allow only the expected scheme(s), and handle rejection without invoking launchUrl.
| for (final platform in platforms) | ||
| PopupMenuItem( | ||
| onTap: () { | ||
| final source = sources.singleWhere( |
There was a problem hiding this comment.
Bug potentiel confirmé : sources.singleWhere((s) => s.platform.name == platform.name) peut lever une StateError (Too many elements). _platforms est dédupliqué par plateforme (via PlatformModel.== sur name), mais sources ne l'est pas. Un épisode peut avoir plusieurs sources sur la même plateforme (ex. Crunchyroll VF + Crunchyroll VOSTFR). Quand un épisode a une plateforme dupliquée + une autre plateforme (→ le menu s'affiche), sélectionner la plateforme dupliquée fait planter singleWhere au runtime. Utiliser une source représentative (firstWhere) ou grouper explicitement au lieu de singleWhere.
Review consolidée Hermes — PR #291Consolidation de 3 reviewers + validations locales. Jalons (inline déjà posés)
|
- Add const PlatformLaunchService (URL validation, launch modes, fallback) - Inject service into GroupedEpisodeViewModel and WeeklyViewModel - Expose onSourcePress event on both ViewModels - Make AvailablePlatformsBadge, GroupedEpisodeCard and ReleaseCard passive widgets receiving onSourcePress closure - Wire PlatformLaunchService in MultiProvider - Apply dart format
- FakeUrlLauncherPlatform and ThrowingUrlLauncherPlatform fakes - Cover URL validation (malformed, non-https), first-mode launch, mode fallback loop, and PlatformException recovery - Cover GroupedEpisodeViewModel and WeeklyViewModel delegation to the launch service with Given/When/Then structure - Add url_launcher_platform_interface as dev dependency