feat(train): signing in to Jellyfin and marking playlists (ADR 0030, D47)

Music's decisions as D47, and its first part: the music screen from the
account sheet, on Android, signs in to the person's Jellyfin with Quick
Connect, else a password, and lists their playlists of music to mark
for running. The token is in the keyring, the server and the marks in
the person's music.json, nothing on Train's server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-29 12:36:32 +01:00
commit 3336fe4eca
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 2364 additions and 3 deletions

View file

@ -468,5 +468,31 @@
"recordingMapHint": "Wischen für die Karte",
"recordingFiguresHint": "Zurückwischen für die Werte",
"recordingNorthUp": "Norden oben",
"recordingHeadingUp": "Bewegungsrichtung oben"
"recordingHeadingUp": "Bewegungsrichtung oben",
"musicTitle": "Musik",
"musicHint": "Jellyfin-Playlists zum Laufen",
"musicIntro": "Train spielt Musik von deinem Jellyfin-Server und wählt Titel, deren Takt zu deinen Schritten passt. Dein Konto und deine Musik bleiben auf diesem Telefon.",
"musicServer": "Jellyfin-Server",
"musicConnect": "Verbinden",
"musicBadAddress": "Gib die Adresse des Servers ein, etwa jellyfin.example.org.",
"musicUnreachable": "Der Server ist nicht erreichbar. Prüfe die Adresse und die Verbindung.",
"musicNotJellyfin": "Unter dieser Adresse ist kein Jellyfin-Server.",
"musicRefused": "Jellyfin hat abgelehnt. Prüfe Benutzername und Passwort.",
"musicCodeExpired": "Der Code ist abgelaufen.",
"musicFailed": "Jellyfin hat mit einem Fehler geantwortet. Versuch es später noch einmal.",
"musicQuickConnect": "Um dich bei {server} anzumelden, gib diesen Code in Jellyfin auf einem anderen Gerät ein, unter Einstellungen, Quick Connect.",
"musicNewCode": "Neuer Code",
"musicBack": "Zurück",
"musicUsePassword": "Mit Passwort",
"musicPasswordIntro": "Melde dich mit deinem Jellyfin-Benutzernamen und Passwort bei {server} an.",
"musicUserName": "Benutzername",
"musicPassword": "Passwort",
"musicSignIn": "Anmelden",
"musicSignedInAs": "Angemeldet als {name}",
"musicSignOut": "Abmelden",
"musicPlaylists": "Playlists zum Laufen",
"musicPlaylistsHint": "Train wählt aus den Titeln der Playlists, die du markierst, und behält sie auf diesem Telefon.",
"musicNoPlaylists": "Auf diesem Server gibt es noch keine Musik-Playlists.",
"musicRetry": "Erneut versuchen",
"musicTracks": "{count, plural, =1{1 Titel} other{{count} Titel}}"
}

View file

@ -588,5 +588,38 @@
"recordingMapHint": "Swipe for the map",
"recordingFiguresHint": "Swipe back for the figures",
"recordingNorthUp": "North up",
"recordingHeadingUp": "Heading up"
"recordingHeadingUp": "Heading up",
"musicTitle": "Music",
"@musicTitle": {"description": "The music screen's title, and its tile in the account sheet."},
"musicHint": "Jellyfin playlists to run to",
"@musicHint": {"description": "Under Music in the account sheet."},
"musicIntro": "Train plays music from your Jellyfin server, choosing tracks whose beat fits your steps. Your account and music stay on this phone.",
"musicServer": "Jellyfin server",
"@musicServer": {"description": "The label of the server's address."},
"musicConnect": "Connect",
"musicBadAddress": "Enter the server's address, such as jellyfin.example.org.",
"musicUnreachable": "The server can't be reached. Check the address and the connection.",
"musicNotJellyfin": "There is no Jellyfin server at this address.",
"musicRefused": "Jellyfin refused. Check the user name and password.",
"musicCodeExpired": "The code has expired.",
"musicFailed": "Jellyfin answered with an error. Try again later.",
"musicQuickConnect": "To sign in to {server}, enter this code in Jellyfin on another device, under Settings, Quick Connect.",
"@musicQuickConnect": {"placeholders": {"server": {"type": "String"}}},
"musicNewCode": "New code",
"musicBack": "Back",
"musicUsePassword": "Use a password",
"musicPasswordIntro": "Sign in to {server} with your Jellyfin user name and password.",
"@musicPasswordIntro": {"placeholders": {"server": {"type": "String"}}},
"musicUserName": "User name",
"musicPassword": "Password",
"musicSignIn": "Sign in",
"musicSignedInAs": "Signed in as {name}",
"@musicSignedInAs": {"placeholders": {"name": {"type": "String"}}},
"musicSignOut": "Sign out",
"musicPlaylists": "Playlists for running",
"musicPlaylistsHint": "Train chooses from the tracks of the playlists you mark, and keeps them on this phone.",
"musicNoPlaylists": "There are no music playlists on this server yet.",
"musicRetry": "Try again",
"musicTracks": "{count, plural, =1{1 track} other{{count} tracks}}",
"@musicTracks": {"placeholders": {"count": {"type": "int"}}}
}

View file

@ -2835,6 +2835,162 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Heading up'**
String get recordingHeadingUp;
/// The music screen's title, and its tile in the account sheet.
///
/// In en, this message translates to:
/// **'Music'**
String get musicTitle;
/// Under Music in the account sheet.
///
/// In en, this message translates to:
/// **'Jellyfin playlists to run to'**
String get musicHint;
/// No description provided for @musicIntro.
///
/// In en, this message translates to:
/// **'Train plays music from your Jellyfin server, choosing tracks whose beat fits your steps. Your account and music stay on this phone.'**
String get musicIntro;
/// The label of the server's address.
///
/// In en, this message translates to:
/// **'Jellyfin server'**
String get musicServer;
/// No description provided for @musicConnect.
///
/// In en, this message translates to:
/// **'Connect'**
String get musicConnect;
/// No description provided for @musicBadAddress.
///
/// In en, this message translates to:
/// **'Enter the server\'s address, such as jellyfin.example.org.'**
String get musicBadAddress;
/// No description provided for @musicUnreachable.
///
/// In en, this message translates to:
/// **'The server can\'t be reached. Check the address and the connection.'**
String get musicUnreachable;
/// No description provided for @musicNotJellyfin.
///
/// In en, this message translates to:
/// **'There is no Jellyfin server at this address.'**
String get musicNotJellyfin;
/// No description provided for @musicRefused.
///
/// In en, this message translates to:
/// **'Jellyfin refused. Check the user name and password.'**
String get musicRefused;
/// No description provided for @musicCodeExpired.
///
/// In en, this message translates to:
/// **'The code has expired.'**
String get musicCodeExpired;
/// No description provided for @musicFailed.
///
/// In en, this message translates to:
/// **'Jellyfin answered with an error. Try again later.'**
String get musicFailed;
/// No description provided for @musicQuickConnect.
///
/// In en, this message translates to:
/// **'To sign in to {server}, enter this code in Jellyfin on another device, under Settings, Quick Connect.'**
String musicQuickConnect(String server);
/// No description provided for @musicNewCode.
///
/// In en, this message translates to:
/// **'New code'**
String get musicNewCode;
/// No description provided for @musicBack.
///
/// In en, this message translates to:
/// **'Back'**
String get musicBack;
/// No description provided for @musicUsePassword.
///
/// In en, this message translates to:
/// **'Use a password'**
String get musicUsePassword;
/// No description provided for @musicPasswordIntro.
///
/// In en, this message translates to:
/// **'Sign in to {server} with your Jellyfin user name and password.'**
String musicPasswordIntro(String server);
/// No description provided for @musicUserName.
///
/// In en, this message translates to:
/// **'User name'**
String get musicUserName;
/// No description provided for @musicPassword.
///
/// In en, this message translates to:
/// **'Password'**
String get musicPassword;
/// No description provided for @musicSignIn.
///
/// In en, this message translates to:
/// **'Sign in'**
String get musicSignIn;
/// No description provided for @musicSignedInAs.
///
/// In en, this message translates to:
/// **'Signed in as {name}'**
String musicSignedInAs(String name);
/// No description provided for @musicSignOut.
///
/// In en, this message translates to:
/// **'Sign out'**
String get musicSignOut;
/// No description provided for @musicPlaylists.
///
/// In en, this message translates to:
/// **'Playlists for running'**
String get musicPlaylists;
/// No description provided for @musicPlaylistsHint.
///
/// In en, this message translates to:
/// **'Train chooses from the tracks of the playlists you mark, and keeps them on this phone.'**
String get musicPlaylistsHint;
/// No description provided for @musicNoPlaylists.
///
/// In en, this message translates to:
/// **'There are no music playlists on this server yet.'**
String get musicNoPlaylists;
/// No description provided for @musicRetry.
///
/// In en, this message translates to:
/// **'Try again'**
String get musicRetry;
/// No description provided for @musicTracks.
///
/// In en, this message translates to:
/// **'{count, plural, =1{1 track} other{{count} tracks}}'**
String musicTracks(int count);
}
class _AppLocalizationsDelegate

View file

@ -1681,4 +1681,104 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get recordingHeadingUp => 'Bewegungsrichtung oben';
@override
String get musicTitle => 'Musik';
@override
String get musicHint => 'Jellyfin-Playlists zum Laufen';
@override
String get musicIntro =>
'Train spielt Musik von deinem Jellyfin-Server und wählt Titel, deren Takt zu deinen Schritten passt. Dein Konto und deine Musik bleiben auf diesem Telefon.';
@override
String get musicServer => 'Jellyfin-Server';
@override
String get musicConnect => 'Verbinden';
@override
String get musicBadAddress =>
'Gib die Adresse des Servers ein, etwa jellyfin.example.org.';
@override
String get musicUnreachable =>
'Der Server ist nicht erreichbar. Prüfe die Adresse und die Verbindung.';
@override
String get musicNotJellyfin =>
'Unter dieser Adresse ist kein Jellyfin-Server.';
@override
String get musicRefused =>
'Jellyfin hat abgelehnt. Prüfe Benutzername und Passwort.';
@override
String get musicCodeExpired => 'Der Code ist abgelaufen.';
@override
String get musicFailed =>
'Jellyfin hat mit einem Fehler geantwortet. Versuch es später noch einmal.';
@override
String musicQuickConnect(String server) {
return 'Um dich bei $server anzumelden, gib diesen Code in Jellyfin auf einem anderen Gerät ein, unter Einstellungen, Quick Connect.';
}
@override
String get musicNewCode => 'Neuer Code';
@override
String get musicBack => 'Zurück';
@override
String get musicUsePassword => 'Mit Passwort';
@override
String musicPasswordIntro(String server) {
return 'Melde dich mit deinem Jellyfin-Benutzernamen und Passwort bei $server an.';
}
@override
String get musicUserName => 'Benutzername';
@override
String get musicPassword => 'Passwort';
@override
String get musicSignIn => 'Anmelden';
@override
String musicSignedInAs(String name) {
return 'Angemeldet als $name';
}
@override
String get musicSignOut => 'Abmelden';
@override
String get musicPlaylists => 'Playlists zum Laufen';
@override
String get musicPlaylistsHint =>
'Train wählt aus den Titeln der Playlists, die du markierst, und behält sie auf diesem Telefon.';
@override
String get musicNoPlaylists =>
'Auf diesem Server gibt es noch keine Musik-Playlists.';
@override
String get musicRetry => 'Erneut versuchen';
@override
String musicTracks(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count Titel',
one: '1 Titel',
);
return '$_temp0';
}
}

View file

@ -1671,6 +1671,104 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get recordingHeadingUp => 'Heading up';
@override
String get musicTitle => 'Music';
@override
String get musicHint => 'Jellyfin playlists to run to';
@override
String get musicIntro =>
'Train plays music from your Jellyfin server, choosing tracks whose beat fits your steps. Your account and music stay on this phone.';
@override
String get musicServer => 'Jellyfin server';
@override
String get musicConnect => 'Connect';
@override
String get musicBadAddress =>
'Enter the server\'s address, such as jellyfin.example.org.';
@override
String get musicUnreachable =>
'The server can\'t be reached. Check the address and the connection.';
@override
String get musicNotJellyfin => 'There is no Jellyfin server at this address.';
@override
String get musicRefused =>
'Jellyfin refused. Check the user name and password.';
@override
String get musicCodeExpired => 'The code has expired.';
@override
String get musicFailed => 'Jellyfin answered with an error. Try again later.';
@override
String musicQuickConnect(String server) {
return 'To sign in to $server, enter this code in Jellyfin on another device, under Settings, Quick Connect.';
}
@override
String get musicNewCode => 'New code';
@override
String get musicBack => 'Back';
@override
String get musicUsePassword => 'Use a password';
@override
String musicPasswordIntro(String server) {
return 'Sign in to $server with your Jellyfin user name and password.';
}
@override
String get musicUserName => 'User name';
@override
String get musicPassword => 'Password';
@override
String get musicSignIn => 'Sign in';
@override
String musicSignedInAs(String name) {
return 'Signed in as $name';
}
@override
String get musicSignOut => 'Sign out';
@override
String get musicPlaylists => 'Playlists for running';
@override
String get musicPlaylistsHint =>
'Train chooses from the tracks of the playlists you mark, and keeps them on this phone.';
@override
String get musicNoPlaylists =>
'There are no music playlists on this server yet.';
@override
String get musicRetry => 'Try again';
@override
String musicTracks(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count tracks',
one: '1 track',
);
return '$_temp0';
}
}
/// The translations for English, as used in the United Kingdom (`en_GB`).