fix(jellyfin): a tap on someone a server shows names them, the password asked
Jellyfin v10.11 and later no longer say which accounts have a password (HasPassword is always true, jellyfin/jellyfin#14950), so the demo's account was listed as "With a password" and only named in the form. Every listed account is now named in the form with the password field focused, which says to leave it empty for an account without one, as Jellyfin's own apps do; the line under each account goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
33584e428e
commit
107160cac4
12 changed files with 62 additions and 66 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1750,9 +1750,8 @@
|
|||
"jellyfinFind": "Weiter",
|
||||
"jellyfinUser": "Benutzername",
|
||||
"jellyfinPublicUsers": "Auf diesem Server",
|
||||
"jellyfinWithoutPassword": "Ohne Passwort: ein Tippen meldet an",
|
||||
"jellyfinWithPassword": "Mit Passwort",
|
||||
"jellyfinPassword": "Passwort",
|
||||
"jellyfinPasswordNone": "Leer lassen, wenn das Konto keins hat",
|
||||
"jellyfinSignIn": "Anmelden",
|
||||
"jellyfinQuickConnect": "Mit einem Code anmelden",
|
||||
"jellyfinQuickConnectCode": "Gib diesen Code unter Quick Connect in einer Jellyfin-App ein, in der du angemeldet bist:",
|
||||
|
|
|
|||
|
|
@ -2062,9 +2062,8 @@
|
|||
"jellyfinFind": "Continue",
|
||||
"jellyfinUser": "User name",
|
||||
"jellyfinPublicUsers": "On this server",
|
||||
"jellyfinWithoutPassword": "No password: one tap signs in",
|
||||
"jellyfinWithPassword": "With a password",
|
||||
"jellyfinPassword": "Password",
|
||||
"jellyfinPasswordNone": "Leave empty if the account has none",
|
||||
"jellyfinSignIn": "Sign in",
|
||||
"jellyfinQuickConnect": "Sign in with a code",
|
||||
"jellyfinQuickConnectCode": "Enter this code under Quick Connect in a Jellyfin app you're signed in to:",
|
||||
|
|
|
|||
|
|
@ -10341,24 +10341,18 @@ abstract class AppLocalizations {
|
|||
/// **'On this server'**
|
||||
String get jellyfinPublicUsers;
|
||||
|
||||
/// No description provided for @jellyfinWithoutPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No password: one tap signs in'**
|
||||
String get jellyfinWithoutPassword;
|
||||
|
||||
/// No description provided for @jellyfinWithPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'With a password'**
|
||||
String get jellyfinWithPassword;
|
||||
|
||||
/// No description provided for @jellyfinPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Password'**
|
||||
String get jellyfinPassword;
|
||||
|
||||
/// No description provided for @jellyfinPasswordNone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Leave empty if the account has none'**
|
||||
String get jellyfinPasswordNone;
|
||||
|
||||
/// No description provided for @jellyfinSignIn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -6521,15 +6521,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get jellyfinPublicUsers => 'Auf diesem Server';
|
||||
|
||||
@override
|
||||
String get jellyfinWithoutPassword => 'Ohne Passwort: ein Tippen meldet an';
|
||||
|
||||
@override
|
||||
String get jellyfinWithPassword => 'Mit Passwort';
|
||||
|
||||
@override
|
||||
String get jellyfinPassword => 'Passwort';
|
||||
|
||||
@override
|
||||
String get jellyfinPasswordNone => 'Leer lassen, wenn das Konto keins hat';
|
||||
|
||||
@override
|
||||
String get jellyfinSignIn => 'Anmelden';
|
||||
|
||||
|
|
|
|||
|
|
@ -6465,15 +6465,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get jellyfinPublicUsers => 'On this server';
|
||||
|
||||
@override
|
||||
String get jellyfinWithoutPassword => 'No password: one tap signs in';
|
||||
|
||||
@override
|
||||
String get jellyfinWithPassword => 'With a password';
|
||||
|
||||
@override
|
||||
String get jellyfinPassword => 'Password';
|
||||
|
||||
@override
|
||||
String get jellyfinPasswordNone => 'Leave empty if the account has none';
|
||||
|
||||
@override
|
||||
String get jellyfinSignIn => 'Sign in';
|
||||
|
||||
|
|
|
|||
|
|
@ -242,11 +242,7 @@ class HttpJellyfinGateway(
|
|||
final id?,
|
||||
final name?,
|
||||
))
|
||||
JellyfinPublicUser(
|
||||
id: id,
|
||||
name: name,
|
||||
hasPassword: json[_key(json, 'HasPassword')] != false,
|
||||
),
|
||||
JellyfinPublicUser(id: id, name: name),
|
||||
];
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -12,15 +12,16 @@ final class const JellyfinServer({
|
|||
List<Object?> get props => [address, id, name, version];
|
||||
}
|
||||
|
||||
/// Someone a Jellyfin server shows on its login screen, to sign in as
|
||||
/// with a tap: a demo's account has no password (decided 2026-10-13).
|
||||
/// Someone a Jellyfin server shows on its login screen, to sign in as.
|
||||
/// Whether they have a password the server doesn't say: since v10.11
|
||||
/// Jellyfin keeps it to itself, so nobody learns which accounts are open
|
||||
/// (decided 2026-10-14).
|
||||
final class const JellyfinPublicUser({
|
||||
required final String id,
|
||||
required final String name,
|
||||
required final bool hasPassword,
|
||||
}) with Equatable {
|
||||
@override
|
||||
List<Object?> get props => [id, name, hasPassword];
|
||||
List<Object?> get props => [id, name];
|
||||
}
|
||||
|
||||
/// Signed in to a Jellyfin server: as whom, and the token every request
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ String jellyfinProblemText(AppLocalizations l10n, JellyfinProblem problem) =>
|
|||
///
|
||||
/// A server the instance suggests is filled in and found at once, and the
|
||||
/// people a server shows on its login screen are listed (decided
|
||||
/// 2026-10-13): one without a password, as a demo's, signs in with a tap,
|
||||
/// one with a password is named in the form.
|
||||
/// 2026-10-13): a tap names one in the form, which asks for the password,
|
||||
/// left empty for an account without one, as a demo's. Jellyfin's own
|
||||
/// apps do the same since the server stopped saying which accounts have
|
||||
/// one (v10.11; decided 2026-10-14).
|
||||
class const JellyfinConnectForm({super.key}) extends StatefulWidget {
|
||||
@override
|
||||
State<JellyfinConnectForm> createState() => _JellyfinConnectFormState();
|
||||
|
|
@ -114,16 +116,12 @@ class _JellyfinConnectFormState() extends State<JellyfinConnectForm> {
|
|||
).jellyfin.signIn(_server!, _user.text.trim(), _password.text),
|
||||
);
|
||||
|
||||
/// One of the people the server shows: signed in at once without a
|
||||
/// password, else named in the form, which asks for the password.
|
||||
/// One of the people the server shows, named in the form, which asks
|
||||
/// for the password.
|
||||
void _choose(JellyfinPublicUser user) {
|
||||
_user.text = user.name;
|
||||
_password.clear();
|
||||
if (user.hasPassword) {
|
||||
_passwordFocus.requestFocus();
|
||||
} else {
|
||||
unawaited(_signIn());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _startQuickConnect() => _run(() async {
|
||||
|
|
@ -269,11 +267,6 @@ class _JellyfinConnectFormState() extends State<JellyfinConnectForm> {
|
|||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
user.hasPassword
|
||||
? l10n.jellyfinWithPassword
|
||||
: l10n.jellyfinWithoutPassword,
|
||||
),
|
||||
enabled: !_busy,
|
||||
onTap: () => _choose(user),
|
||||
),
|
||||
|
|
@ -302,6 +295,7 @@ class _JellyfinConnectFormState() extends State<JellyfinConnectForm> {
|
|||
textInputAction: TextInputAction.go,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.jellyfinPassword,
|
||||
helperText: l10n.jellyfinPasswordNone,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
onSubmitted: _busy ? null : (_) => _signIn(),
|
||||
|
|
|
|||
|
|
@ -238,15 +238,19 @@ void main() {
|
|||
|
||||
group('a Jellyfin server the instance suggests (decided 2026-10-13)', () {
|
||||
setUp(() {
|
||||
jellyfin.shownUsers = const [
|
||||
JellyfinPublicUser(id: 'user-2', name: 'Demo', hasPassword: false),
|
||||
JellyfinPublicUser(id: 'user-1', name: 'mika', hasPassword: true),
|
||||
];
|
||||
jellyfin
|
||||
..shownUsers = const [
|
||||
JellyfinPublicUser(id: 'user-2', name: 'Demo'),
|
||||
JellyfinPublicUser(id: 'user-1', name: 'mika'),
|
||||
]
|
||||
..withoutPassword = const {'Demo'};
|
||||
});
|
||||
|
||||
testWidgets('Add material offers it before signing in; the sign-in '
|
||||
"finds it by itself and lists the server's public people, one "
|
||||
'without a password signed in with a tap', (tester) async {
|
||||
'without a password signed in with the password left empty', (
|
||||
tester,
|
||||
) async {
|
||||
await show(
|
||||
tester,
|
||||
RoutePaths.studyset('japanese'),
|
||||
|
|
@ -262,13 +266,16 @@ void main() {
|
|||
expect(find.text('Demo'), findsOneWidget);
|
||||
await tester.tap(find.text('Demo'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(store.kept?.account, isNull);
|
||||
await tester.tap(find.text('Sign in'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Libraries'), findsOneWidget);
|
||||
expect(store.kept?.account?.userName, 'Demo');
|
||||
});
|
||||
|
||||
testWidgets('one with a password is named in the form, which asks for '
|
||||
'it', (tester) async {
|
||||
testWidgets('a tap names one in the form, which asks for the '
|
||||
'password', (tester) async {
|
||||
await show(
|
||||
tester,
|
||||
RoutePaths.jellyfin('japanese'),
|
||||
|
|
@ -285,6 +292,13 @@ void main() {
|
|||
.text,
|
||||
'mika',
|
||||
);
|
||||
expect(
|
||||
tester
|
||||
.widget<TextField>(find.widgetWithText(TextField, 'Password'))
|
||||
.focusNode!
|
||||
.hasFocus,
|
||||
isTrue,
|
||||
);
|
||||
expect(store.kept?.account, isNull);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ MockClient jellyfin12(List<http.Request> requests) => MockClient((
|
|||
'id': 'e39b17f2',
|
||||
'startupWizardCompleted': true,
|
||||
}),
|
||||
// The people the server shows on its login screen, one without a
|
||||
// password (a demo's), in either case of field names.
|
||||
// The people the server shows on its login screen, in either case of
|
||||
// field names; whether one has a password, which servers before v10.11
|
||||
// said, counts for nothing.
|
||||
('GET', '/Users/Public') => json([
|
||||
{'Name': 'Demo', 'Id': 'u-2', 'HasPassword': false},
|
||||
{'name': 'mika', 'id': 'u-1', 'hasPassword': true},
|
||||
|
|
@ -236,15 +237,14 @@ void main() {
|
|||
},
|
||||
);
|
||||
|
||||
test('lists the people a server shows on its login screen, and which '
|
||||
'need a password', () async {
|
||||
test('lists the people a server shows on its login screen', () async {
|
||||
final server = await gateway.server(
|
||||
Uri.parse('http://jellyfin.local:8096'),
|
||||
);
|
||||
|
||||
expect(await gateway.publicUsers(server), const [
|
||||
JellyfinPublicUser(id: 'u-2', name: 'Demo', hasPassword: false),
|
||||
JellyfinPublicUser(id: 'u-1', name: 'mika', hasPassword: true),
|
||||
JellyfinPublicUser(id: 'u-2', name: 'Demo'),
|
||||
JellyfinPublicUser(id: 'u-1', name: 'mika'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,13 @@ class FakeJellyfinGateway({
|
|||
/// Whether the Quick Connect code was approved.
|
||||
bool approved = false;
|
||||
|
||||
/// The people the server shows on its login screen; those without a
|
||||
/// password sign in with none.
|
||||
/// The people the server shows on its login screen; those named in
|
||||
/// [withoutPassword] sign in with none.
|
||||
List<JellyfinPublicUser> shownUsers = const [];
|
||||
|
||||
/// Names of accounts without a password, as a demo's.
|
||||
Set<String> withoutPassword = const {};
|
||||
|
||||
/// What was reported of playbacks: their positions, in order.
|
||||
final reports = <({Duration position, bool started, bool stopped})>[];
|
||||
|
||||
|
|
@ -90,7 +93,9 @@ class FakeJellyfinGateway({
|
|||
String password,
|
||||
) async {
|
||||
for (final public in shownUsers) {
|
||||
if (public.name == user && !public.hasPassword && password.isEmpty) {
|
||||
if (public.name == user &&
|
||||
withoutPassword.contains(user) &&
|
||||
password.isEmpty) {
|
||||
return _account('token-3', userId: public.id, userName: public.name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue