From dcc06768e07e3030bd31788f7ddd4ecd6933c9c6 Mon Sep 17 00:00:00 2001 From: Jack Danger Date: Tue, 4 Aug 2026 09:46:52 -0700 Subject: [PATCH] fix(profiles): label a Plex Home parent connection as an account A Plex Home profile's chip rendered the parent connection's displayLabel, which for a Plex account is the account owner's username. The owner's name appeared directly beneath the Home user's own, reading as the wrong user being signed in. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Fdzda9t7kQtq7LoQ2v5nVF --- lib/i18n/en.i18n.json | 1 + lib/i18n/strings.g.dart | 2 +- lib/i18n/strings_en.g.dart | 8 ++- .../profile/profile_switch_screen.dart | 12 +++- .../profile/profile_switch_screen_test.dart | 64 +++++++++++++++++++ 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 3353a0bc..dbe06f2e 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -763,6 +763,7 @@ "deleteThisProfileTitle": "Delete this profile?", "deleteThisProfileMessage": "Remove ${displayName}. Connections aren't affected.", "active": "Active", + "viaPlexAccount": "via ${name}", "manage": "Manage", "delete": "Delete", "signOut": "Sign out", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index b28c6fc9..3f2ac099 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,7 +4,7 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 22 -/// Strings: 32946 (1497 per locale) +/// Strings: 32947 (1497 per locale) // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 59a7893b..e68c8171 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -2296,6 +2296,9 @@ class Translations$profiles$en { /// en: 'Active' String get active => 'Active'; + /// en: 'via ${name}' + String viaPlexAccount({required Object name}) => 'via ${name}'; + /// en: 'Manage' String get manage => 'Manage'; @@ -6751,6 +6754,7 @@ extension on Translations { 'profiles.deleteThisProfileTitle' => 'Delete this profile?', 'profiles.deleteThisProfileMessage' => ({required Object displayName}) => 'Remove ${displayName}. Connections aren\'t affected.', 'profiles.active' => 'Active', + 'profiles.viaPlexAccount' => ({required Object name}) => 'via ${name}', 'profiles.manage' => 'Manage', 'profiles.delete' => 'Delete', 'profiles.signOut' => 'Sign out', @@ -7059,9 +7063,9 @@ extension on Translations { 'explore.stats.onHold' => ({required Object n}) => '${n} on hold', 'explore.stats.dropped' => ({required Object n}) => '${n} dropped', 'explore.season.winter' => 'Winter', - 'explore.season.spring' => 'Spring', _ => null, } ?? switch (path) { + 'explore.season.spring' => 'Spring', 'explore.season.summer' => 'Summer', 'explore.season.fall' => 'Fall', 'explore.season.withYear' => ({required Object season, required Object year}) => '${season} ${year}', @@ -7573,9 +7577,9 @@ extension on Translations { 'externalPlayer.playerPackage' => 'Package Name', 'externalPlayer.playerUrlScheme' => 'URL Scheme', 'externalPlayer.off' => 'Off', - 'externalPlayer.launchFailed' => 'Failed to open external player', _ => null, } ?? switch (path) { + 'externalPlayer.launchFailed' => 'Failed to open external player', 'externalPlayer.appNotInstalled' => ({required Object name}) => '${name} is not installed', 'externalPlayer.playInExternalPlayer' => 'Play in External Player', 'metadataEdit.editMetadata' => 'Edit...', diff --git a/lib/screens/profile/profile_switch_screen.dart b/lib/screens/profile/profile_switch_screen.dart index 74f4ce11..4c500922 100644 --- a/lib/screens/profile/profile_switch_screen.dart +++ b/lib/screens/profile/profile_switch_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; +import '../../connection/connection.dart'; import '../../connection/connection_registry.dart'; import '../../focus/focusable_wrapper.dart'; import '../../i18n/strings.g.dart'; @@ -320,7 +321,16 @@ class _ProfileSwitchScreenState extends State with MountedS final parentId = profile.parentConnectionId; if (parentId != null) { final conn = view.connectionsById[parentId]; - if (conn != null) chips.add(_ChipData(backend: conn.backend, label: conn.displayLabel)); + if (conn != null) { + chips.add( + _ChipData( + backend: conn.backend, + label: conn is PlexAccountConnection + ? t.profiles.viaPlexAccount(name: conn.accountLabel) + : conn.displayLabel, + ), + ); + } } } final pcs = visibleProfileConnections( diff --git a/test/screens/profile/profile_switch_screen_test.dart b/test/screens/profile/profile_switch_screen_test.dart index 07819f7e..e0c2d8f0 100644 --- a/test/screens/profile/profile_switch_screen_test.dart +++ b/test/screens/profile/profile_switch_screen_test.dart @@ -216,6 +216,70 @@ void main() { await tester.pumpWidget(const SizedBox.shrink()); await tester.pump(); }); + + testWidgets('labels a Plex Home parent connection as an account, not as the profile', (tester) async { + final db = AppDatabase.forTesting(NativeDatabase.memory()); + final profile = Profile.plexHome( + id: 'home-alice', + displayName: 'Alice', + parentConnectionId: 'plex-account', + plexHomeUserUuid: 'alice-uuid', + createdAt: DateTime(2026, 1, 1), + ); + final profiles = _FakeProfileRegistry(db, [profile]); + final connections = _FakeConnectionRegistry(db, [ + PlexAccountConnection( + id: 'plex-account', + accountToken: 'token', + clientIdentifier: 'client', + accountLabel: 'Bob', + createdAt: DateTime(2026, 1, 1), + ), + ]); + final profileConnections = _FakeProfileConnectionRegistry(db); + final storage = await StorageService.getInstance(); + final plexHome = PlexHomeService( + connections: connections, + profileConnections: profileConnections, + storage: storage, + plexHomeUserFetcher: (_) async => const [], + ); + final activeProfile = ActiveProfileProvider( + registry: profiles, + plexHome: plexHome, + connections: connections, + profileConnections: profileConnections, + storage: storage, + ); + addTearDown(() async { + activeProfile.dispose(); + await plexHome.dispose(); + await db.close(); + }); + + await tester.pumpWidget( + TranslationProvider( + child: MultiProvider( + providers: [ + Provider.value(value: profiles), + Provider.value(value: profileConnections), + Provider.value(value: connections), + Provider.value(value: plexHome), + ChangeNotifierProvider.value(value: activeProfile), + ], + child: MaterialApp(theme: monoTheme(dark: true), home: const ProfileSwitchScreen()), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('Alice'), findsOneWidget); + expect(find.text(t.profiles.viaPlexAccount(name: 'Bob')), findsOneWidget); + expect(find.text('Bob'), findsNothing); + + await tester.pumpWidget(const SizedBox.shrink()); + await tester.pump(); + }); } class _FakeProfileRegistry extends ProfileRegistry {