YouTube-Tab: Server-User brauchen keine eigene Baka-Anmeldung mehr
Server-User (Dustin, Baka, Tinker) melden sich beim Öffnen des YouTube-Bereichs automatisch im Hintergrund mit ihren Navidrome- Zugangsdaten beim Baka-Konto an (BakaAuth.autoAnmelden) — die manuelle Anmeldung bleibt nur für Gäste sichtbar. Schlägt die Auto-Anmeldung fehl, gibt es einen Rückfallweg zur manuellen Anmeldung, statt den User auszusperren. Login-Buttons vergrößert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcDiyJdVRqh1TtJk5JiabX
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f0313724d4
commit
f604dc7635
@@ -5,6 +5,37 @@ Format angelehnt an [Keep a Changelog](https://keepachangelog.com/de/1.0.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### 📺🔑 YouTube-Tab: Server-User brauchen keine eigene Baka-Anmeldung mehr (2026-08-26)
|
||||||
|
|
||||||
|
- 🔑 **Server-User (Dustin, Baka, Tinker) sehen den Baka-Anmelde-Schritt nicht
|
||||||
|
mehr.** Bisher verlangte der YouTube-Downloader/die YT-Suche IMMER eine
|
||||||
|
eigene, separate Anmeldung am Baka-Konto — unabhängig davon, ob man schon
|
||||||
|
am Melo-Server angemeldet war. Neu: Wer mit dem Melo-Server verbunden ist
|
||||||
|
(`NavidromeService.istVerbunden`), meldet sich beim Öffnen des
|
||||||
|
YouTube-Bereichs automatisch im Hintergrund mit denselben Zugangsdaten
|
||||||
|
beim Baka-Konto an (`BakaAuth.autoAnmelden`, neu in `baka_auth.dart`) —
|
||||||
|
kein Dialog, kein Warten. Voraussetzung: Navidrome- und Baka-Konto teilen
|
||||||
|
sich Benutzername + Passwort (so bei Dustin/Baka/Tinker der Fall).
|
||||||
|
Schlägt die Hintergrund-Anmeldung fehl (z. B. abweichendes Passwort),
|
||||||
|
bekommt der Server-User den bisherigen manuellen Anmelde-Weg als
|
||||||
|
Rückfalloption zurück — sonst gäbe es keinen Weg mehr zu `_AnmeldeDialog`,
|
||||||
|
das war ein im @code-review-Fund entdeckter Aussperr-Bug (siehe
|
||||||
|
`pruefeServerUser` in `baka_auth.dart`).
|
||||||
|
- 👋 **Die Anmeldung ist jetzt wirklich nur noch für Gäste sichtbar.** Ohne
|
||||||
|
Server-Verbindung (kein eigener Melo-Account) erscheint weiterhin der
|
||||||
|
Hinweistext + Anmelde-Button wie bisher — Server-User sehen stattdessen
|
||||||
|
direkt das Adressfeld, ganz ohne Konto-Status-Zeile.
|
||||||
|
- 🎨 **Login-UI vergrößert.** "Beim Baka-Konto anmelden" ist jetzt ein
|
||||||
|
Button über die volle Breite (52 px hoch, größere Schrift); die Buttons
|
||||||
|
"Abbrechen"/"Anmelden" im Anmelde-Dialog haben jetzt eine Mindesthöhe von
|
||||||
|
48 px. Der Dialog hatte ohnehin nie ein Adressfeld — die Proxy-URL bleibt
|
||||||
|
fest im Code hinterlegt, nicht sichtbar oder änderbar.
|
||||||
|
- ✅ **Getestet:** 582 Tests grün (vorher 574), `flutter analyze` ohne
|
||||||
|
Befund. 8 neue Tests: `BakaAuth.autoAnmelden` (meldet an, greift nicht
|
||||||
|
ein wenn schon angemeldet, tut nichts ohne Zugangsdaten) sowie
|
||||||
|
Widget-Tests für den Server-User-Weg und den Rückfallweg bei
|
||||||
|
fehlgeschlagener Auto-Anmeldung in Download-Tab und YT-Suche-Tab.
|
||||||
|
|
||||||
### 🎚️📺 Equalizer-Fix (Xiaomi) + In-App-YouTube-Suche, Extra-Tab (2026-08-25)
|
### 🎚️📺 Equalizer-Fix (Xiaomi) + In-App-YouTube-Suche, Extra-Tab (2026-08-25)
|
||||||
|
|
||||||
- 🎚️ **Equalizer: "kein System-Klangeffekte" trotz Dolby Atmos behoben.**
|
- 🎚️ **Equalizer: "kein System-Klangeffekte" trotz Dolby Atmos behoben.**
|
||||||
|
|||||||
@@ -606,11 +606,32 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
|
|||||||
/// Gewählter Datenträger; `null` = Android entscheidet (nur ein Ort da).
|
/// Gewählter Datenträger; `null` = Android entscheidet (nur ein Ort da).
|
||||||
String? _volume;
|
String? _volume;
|
||||||
|
|
||||||
|
/// Wer am Melo-Server angemeldet ist, braucht keine eigene
|
||||||
|
/// Baka-Anmeldung mehr — nur Gäste (kein Server-Account) sehen sie.
|
||||||
|
bool _serverUser = false;
|
||||||
|
|
||||||
|
/// Die automatische Anmeldung ist fehlgeschlagen (z. B. abweichendes
|
||||||
|
/// Baka-Passwort) — dann bleibt der manuelle Anmelde-Weg als Rückfalloption
|
||||||
|
/// sichtbar, sonst käme so ein User nie mehr an den Dialog heran.
|
||||||
|
bool _autoLoginFehlgeschlagen = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_ladeEinstellungen();
|
_ladeEinstellungen();
|
||||||
_uebernimmAdresse();
|
_uebernimmAdresse();
|
||||||
|
_pruefeServerUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pruefeServerUser() async {
|
||||||
|
final nav = context.read<DownloadService>().navidrome;
|
||||||
|
final auth = context.read<BakaAuth>();
|
||||||
|
final ergebnis = await pruefeServerUser(nav, auth);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_serverUser = ergebnis.serverUser;
|
||||||
|
_autoLoginFehlgeschlagen = ergebnis.autoLoginFehlgeschlagen;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -716,38 +737,54 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final auth = context.watch<BakaAuth>();
|
final auth = context.watch<BakaAuth>();
|
||||||
final dienst = context.watch<YtDownloadService>();
|
final dienst = context.watch<YtDownloadService>();
|
||||||
|
// Server-User gelten sofort als berechtigt — die Anmeldung passiert für
|
||||||
|
// sie im Hintergrund (_pruefeServerUser). Nur wenn die automatische
|
||||||
|
// Anmeldung nachweislich fehlgeschlagen ist, bleibt der manuelle Weg
|
||||||
|
// als Rückfalloption sichtbar — sonst gäbe es keinen Weg mehr zurück.
|
||||||
|
final zugriffOk =
|
||||||
|
auth.istAngemeldet || (_serverUser && !_autoLoginFehlgeschlagen);
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||||
children: [
|
children: [
|
||||||
if (!auth.istAngemeldet) ...[
|
if (!zugriffOk) ...[
|
||||||
const Text(
|
Text(
|
||||||
'Der Downloader läuft über den Baka-Server. Dafür brauchst du '
|
_autoLoginFehlgeschlagen
|
||||||
'deine Baka-Anmeldung.',
|
? 'Die automatische Anmeldung hat nicht geklappt. Bitte '
|
||||||
style: TextStyle(color: MeloTheme.text2, fontSize: 13),
|
'manuell beim Baka-Konto anmelden.'
|
||||||
|
: 'Der Downloader läuft über den Baka-Server. Dafür brauchst '
|
||||||
|
'du deine Baka-Anmeldung.',
|
||||||
|
style: const TextStyle(color: MeloTheme.text2, fontSize: 13),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
FilledButton.icon(
|
SizedBox(
|
||||||
icon: const Icon(Icons.login),
|
width: double.infinity,
|
||||||
label: const Text('Beim Baka-Konto anmelden'),
|
height: MeloTheme.minTouchTarget + 4,
|
||||||
onPressed: _anmelden,
|
child: FilledButton.icon(
|
||||||
|
icon: const Icon(Icons.login),
|
||||||
|
label: const Text('Beim Baka-Konto anmelden',
|
||||||
|
style: TextStyle(fontSize: 16)),
|
||||||
|
onPressed: _anmelden,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
] else ...[
|
] else ...[
|
||||||
Row(
|
if (!_serverUser) ...[
|
||||||
children: [
|
Row(
|
||||||
const Icon(Icons.verified_user, size: 18, color: MeloTheme.text3),
|
children: [
|
||||||
const SizedBox(width: 8),
|
const Icon(Icons.verified_user, size: 18, color: MeloTheme.text3),
|
||||||
Expanded(
|
const SizedBox(width: 8),
|
||||||
child: Text('Angemeldet als ${auth.benutzer}',
|
Expanded(
|
||||||
style: const TextStyle(color: MeloTheme.text2, fontSize: 13)),
|
child: Text('Angemeldet als ${auth.benutzer}',
|
||||||
),
|
style: const TextStyle(color: MeloTheme.text2, fontSize: 13)),
|
||||||
TextButton(
|
),
|
||||||
onPressed: auth.abmelden,
|
TextButton(
|
||||||
child: const Text('Abmelden'),
|
onPressed: auth.abmelden,
|
||||||
),
|
child: const Text('Abmelden'),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
TextField(
|
TextField(
|
||||||
controller: _url,
|
controller: _url,
|
||||||
enabled: !dienst.laeuft,
|
enabled: !dienst.laeuft,
|
||||||
@@ -909,14 +946,19 @@ class _AnmeldeDialogState extends State<_AnmeldeDialog> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
minimumSize: const Size(88, MeloTheme.minTouchTarget)),
|
||||||
onPressed: _verbindet ? null : () => Navigator.pop(context),
|
onPressed: _verbindet ? null : () => Navigator.pop(context),
|
||||||
child: const Text('Abbrechen'),
|
child: const Text('Abbrechen', style: TextStyle(fontSize: 16)),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
minimumSize: const Size(120, MeloTheme.minTouchTarget)),
|
||||||
onPressed: _verbindet ? null : _anmelden,
|
onPressed: _verbindet ? null : _anmelden,
|
||||||
child: const Text('Anmelden'),
|
child: const Text('Anmelden', style: TextStyle(fontSize: 16)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
|||||||
import '../library/database.dart';
|
import '../library/database.dart';
|
||||||
import '../library/library_service.dart';
|
import '../library/library_service.dart';
|
||||||
import '../services/baka_auth.dart';
|
import '../services/baka_auth.dart';
|
||||||
|
import '../services/download_service.dart';
|
||||||
import '../services/media_store.dart';
|
import '../services/media_store.dart';
|
||||||
import '../services/yt_download_service.dart';
|
import '../services/yt_download_service.dart';
|
||||||
import '../services/yt_search_service.dart';
|
import '../services/yt_search_service.dart';
|
||||||
@@ -25,6 +26,31 @@ class YoutubeSearchScreen extends StatefulWidget {
|
|||||||
class _YoutubeSearchScreenState extends State<YoutubeSearchScreen> {
|
class _YoutubeSearchScreenState extends State<YoutubeSearchScreen> {
|
||||||
final _query = TextEditingController();
|
final _query = TextEditingController();
|
||||||
|
|
||||||
|
/// Server-User (siehe _YouTubeBereich in downloads_screen.dart) brauchen
|
||||||
|
/// hier keinen Anmelde-Hinweis — die Anmeldung passiert im Hintergrund.
|
||||||
|
bool _serverUser = false;
|
||||||
|
|
||||||
|
/// Die automatische Anmeldung ist fehlgeschlagen — dann bleibt der
|
||||||
|
/// Hinweis auf die manuelle Anmeldung (siehe Download-Tab) sichtbar.
|
||||||
|
bool _autoLoginFehlgeschlagen = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_pruefeServerUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pruefeServerUser() async {
|
||||||
|
final nav = context.read<DownloadService>().navidrome;
|
||||||
|
final auth = context.read<BakaAuth>();
|
||||||
|
final ergebnis = await pruefeServerUser(nav, auth);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_serverUser = ergebnis.serverUser;
|
||||||
|
_autoLoginFehlgeschlagen = ergebnis.autoLoginFehlgeschlagen;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_query.dispose();
|
_query.dispose();
|
||||||
@@ -42,6 +68,8 @@ class _YoutubeSearchScreenState extends State<YoutubeSearchScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final auth = context.watch<BakaAuth>();
|
final auth = context.watch<BakaAuth>();
|
||||||
final suche = context.watch<YtSearchService>();
|
final suche = context.watch<YtSearchService>();
|
||||||
|
final zugriffOk =
|
||||||
|
auth.istAngemeldet || (_serverUser && !_autoLoginFehlgeschlagen);
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -69,7 +97,7 @@ class _YoutubeSearchScreenState extends State<YoutubeSearchScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!auth.istAngemeldet)
|
if (!zugriffOk)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import 'navidrome_service.dart';
|
||||||
|
|
||||||
/// Ablage für den Anmelde-Token. Als Schnittstelle, damit Tests ohne die
|
/// Ablage für den Anmelde-Token. Als Schnittstelle, damit Tests ohne die
|
||||||
/// Plattform-Kanäle von FlutterSecureStorage auskommen.
|
/// Plattform-Kanäle von FlutterSecureStorage auskommen.
|
||||||
abstract class TokenSpeicher {
|
abstract class TokenSpeicher {
|
||||||
@@ -100,6 +102,15 @@ class BakaAuth extends ChangeNotifier {
|
|||||||
'Anmeldung fehlgeschlagen (${antwort.statusCode})';
|
'Anmeldung fehlgeschlagen (${antwort.statusCode})';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Meldet automatisch an, falls noch nicht angemeldet — für Server-User
|
||||||
|
/// (Dustin, Baka, Tinker), die keine eigene Baka-Anmeldung mehr sehen
|
||||||
|
/// sollen: der Proxy nutzt seine YouTube-Cookies für jeden
|
||||||
|
/// authentifizierten Aufruf ohnehin automatisch.
|
||||||
|
Future<void> autoAnmelden(String benutzer, String passwort) async {
|
||||||
|
if (istAngemeldet || benutzer.isEmpty || passwort.isEmpty) return;
|
||||||
|
await anmelden(benutzer, passwort);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> abmelden() async {
|
Future<void> abmelden() async {
|
||||||
_token = null;
|
_token = null;
|
||||||
_benutzer = '';
|
_benutzer = '';
|
||||||
@@ -108,3 +119,20 @@ class BakaAuth extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prüft, ob ein Server-User verbunden ist, und meldet ihn dann automatisch
|
||||||
|
/// beim Baka-Konto an — geteilte Logik für den YouTube-Bereich
|
||||||
|
/// (downloads_screen.dart) und die YT-Suche (youtube_search_screen.dart).
|
||||||
|
/// Gäste (kein Server-Account) bleiben unangetastet. Schlägt die
|
||||||
|
/// Hintergrund-Anmeldung fehl (falsche/fehlende Zugangsdaten, Server nicht
|
||||||
|
/// erreichbar), meldet [autoLoginFehlgeschlagen] das zurück — sonst bliebe
|
||||||
|
/// so ein User ohne jeden Weg mehr zur manuellen Baka-Anmeldung.
|
||||||
|
Future<({bool serverUser, bool autoLoginFehlgeschlagen})> pruefeServerUser(
|
||||||
|
NavidromeService navidrome, BakaAuth auth) async {
|
||||||
|
await navidrome.ladeGespeicherteZugangsdaten();
|
||||||
|
if (!navidrome.istVerbunden) {
|
||||||
|
return (serverUser: false, autoLoginFehlgeschlagen: false);
|
||||||
|
}
|
||||||
|
await auth.autoAnmelden(navidrome.benutzer, navidrome.passwort);
|
||||||
|
return (serverUser: true, autoLoginFehlgeschlagen: !auth.istAngemeldet);
|
||||||
|
}
|
||||||
|
|||||||
@@ -140,6 +140,12 @@ class NavidromeService {
|
|||||||
|
|
||||||
bool get istVerbunden => _serverUrl.isNotEmpty && _user.isNotEmpty;
|
bool get istVerbunden => _serverUrl.isNotEmpty && _user.isNotEmpty;
|
||||||
|
|
||||||
|
/// Für die automatische Baka-Anmeldung verbundener Server-User (siehe
|
||||||
|
/// [BakaAuth.autoAnmelden]) — Navidrome- und Baka-Konto teilen sich die
|
||||||
|
/// Zugangsdaten.
|
||||||
|
String get benutzer => _user;
|
||||||
|
String get passwort => _password;
|
||||||
|
|
||||||
/// Dekodiert eine Subsonic-JSON-Antwort und wirft [NavidromeException],
|
/// Dekodiert eine Subsonic-JSON-Antwort und wirft [NavidromeException],
|
||||||
/// wenn der Server `status:"failed"` meldet (z. B. falsches Passwort) —
|
/// wenn der Server `status:"failed"` meldet (z. B. falsches Passwort) —
|
||||||
/// solche Antworten kommen mit HTTP 200 und dürfen nicht als „leer" gelten.
|
/// solche Antworten kommen mit HTTP 200 und dürfen nicht als „leer" gelten.
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ class _MemorySpeicher implements TokenSpeicher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _wrap(BakaAuth auth,
|
Widget _wrap(BakaAuth auth,
|
||||||
{required MeloDb db, required CategoryService categories}) {
|
{required MeloDb db,
|
||||||
|
required CategoryService categories,
|
||||||
|
NavidromeService? navidrome}) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider<MeloDb>.value(value: db),
|
Provider<MeloDb>.value(value: db),
|
||||||
@@ -45,7 +47,8 @@ Widget _wrap(BakaAuth auth,
|
|||||||
// Nur fürs Badge im Unterreiter-Kopf gebraucht (dienst.laeuft) — der
|
// Nur fürs Badge im Unterreiter-Kopf gebraucht (dienst.laeuft) — der
|
||||||
// Download-Tab beobachtet ihn app-weit, unabhängig vom Unterreiter.
|
// Download-Tab beobachtet ihn app-weit, unabhängig vom Unterreiter.
|
||||||
ChangeNotifierProvider<DownloadService>(
|
ChangeNotifierProvider<DownloadService>(
|
||||||
create: (_) => DownloadService(db: db, navidrome: NavidromeService()),
|
create: (_) => DownloadService(
|
||||||
|
db: db, navidrome: navidrome ?? NavidromeService()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: const MaterialApp(home: Scaffold(body: DownloadsScreen())),
|
child: const MaterialApp(home: Scaffold(body: DownloadsScreen())),
|
||||||
@@ -79,6 +82,14 @@ void main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// _YouTubeBereich prüft beim Öffnen per NavidromeService.
|
||||||
|
// ladeGespeicherteZugangsdaten(), ob ein Server-User verbunden ist — ohne
|
||||||
|
// diese Antwort bleibt der Aufruf im Test unbeantwortet hängen.
|
||||||
|
messenger.setMockMethodCallHandler(
|
||||||
|
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
||||||
|
(call) async => null,
|
||||||
|
);
|
||||||
|
|
||||||
late MeloDb db;
|
late MeloDb db;
|
||||||
late CategoryService categories;
|
late CategoryService categories;
|
||||||
|
|
||||||
@@ -196,4 +207,67 @@ void main() {
|
|||||||
|
|
||||||
expect(find.byType(DropdownButtonFormField<String>), findsNothing);
|
expect(find.byType(DropdownButtonFormField<String>), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Server-User sieht das Adressfeld sofort, ohne Baka-Anmeldung',
|
||||||
|
(tester) async {
|
||||||
|
final auth = BakaAuth(
|
||||||
|
client: MockClient(
|
||||||
|
(_) async => http.Response(jsonEncode({'token': 'jwt'}), 200)),
|
||||||
|
speicher: _MemorySpeicher(),
|
||||||
|
);
|
||||||
|
final nav = NavidromeService()
|
||||||
|
..setCredentials('https://musik.baka-net.de', 'Baka', 'geheim');
|
||||||
|
|
||||||
|
await tester.pumpWidget(_wrap(auth,
|
||||||
|
db: db, categories: categories, navidrome: nav));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Beim Baka-Konto anmelden'), findsNothing);
|
||||||
|
expect(find.text('Angemeldet als Baka'), findsNothing);
|
||||||
|
expect(find.text('YouTube-Adresse'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Server-User bekommt bei fehlgeschlagener Auto-Anmeldung einen '
|
||||||
|
'Rückfallweg zur manuellen Anmeldung', (tester) async {
|
||||||
|
final auth = BakaAuth(
|
||||||
|
client: MockClient((_) async => http.Response('', 500)),
|
||||||
|
speicher: _MemorySpeicher());
|
||||||
|
final nav = NavidromeService()
|
||||||
|
..setCredentials('https://musik.baka-net.de', 'Baka', 'falsch');
|
||||||
|
|
||||||
|
await tester.pumpWidget(_wrap(auth,
|
||||||
|
db: db, categories: categories, navidrome: nav));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('Beim Baka-Konto anmelden'), findsOneWidget);
|
||||||
|
expect(find.text('YouTube-Adresse'), findsNothing);
|
||||||
|
expect(
|
||||||
|
find.textContaining('automatische Anmeldung hat nicht geklappt'),
|
||||||
|
findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Server-User meldet sich im Hintergrund mit denselben Zugangsdaten '
|
||||||
|
'beim Baka-Konto an', (tester) async {
|
||||||
|
String? gesendeterBenutzer;
|
||||||
|
final auth = BakaAuth(
|
||||||
|
client: MockClient((request) async {
|
||||||
|
final body = jsonDecode(request.body) as Map<String, dynamic>;
|
||||||
|
gesendeterBenutzer = body['username'] as String?;
|
||||||
|
return http.Response(jsonEncode({'token': 'jwt'}), 200);
|
||||||
|
}),
|
||||||
|
speicher: _MemorySpeicher(),
|
||||||
|
);
|
||||||
|
final nav = NavidromeService()
|
||||||
|
..setCredentials('https://musik.baka-net.de', 'Tinker', 'geheim2');
|
||||||
|
|
||||||
|
await tester.pumpWidget(_wrap(auth,
|
||||||
|
db: db, categories: categories, navidrome: nav));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(gesendeterBenutzer, 'Tinker');
|
||||||
|
expect(auth.istAngemeldet, isTrue);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:http/testing.dart';
|
import 'package:http/testing.dart';
|
||||||
@@ -9,6 +10,8 @@ import 'package:melo/downloads/youtube_search_screen.dart';
|
|||||||
import 'package:melo/library/database.dart';
|
import 'package:melo/library/database.dart';
|
||||||
import 'package:melo/library/library_service.dart';
|
import 'package:melo/library/library_service.dart';
|
||||||
import 'package:melo/services/baka_auth.dart';
|
import 'package:melo/services/baka_auth.dart';
|
||||||
|
import 'package:melo/services/download_service.dart';
|
||||||
|
import 'package:melo/services/navidrome_service.dart';
|
||||||
import 'package:melo/services/yt_download_service.dart';
|
import 'package:melo/services/yt_download_service.dart';
|
||||||
import 'package:melo/services/yt_search_service.dart';
|
import 'package:melo/services/yt_search_service.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -39,6 +42,7 @@ Widget _wrap({
|
|||||||
required BakaAuth auth,
|
required BakaAuth auth,
|
||||||
required YtSearchService search,
|
required YtSearchService search,
|
||||||
required YtDownloadService download,
|
required YtDownloadService download,
|
||||||
|
NavidromeService? navidrome,
|
||||||
}) {
|
}) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@@ -47,6 +51,13 @@ Widget _wrap({
|
|||||||
ChangeNotifierProvider<BakaAuth>.value(value: auth),
|
ChangeNotifierProvider<BakaAuth>.value(value: auth),
|
||||||
ChangeNotifierProvider<YtSearchService>.value(value: search),
|
ChangeNotifierProvider<YtSearchService>.value(value: search),
|
||||||
ChangeNotifierProvider<YtDownloadService>.value(value: download),
|
ChangeNotifierProvider<YtDownloadService>.value(value: download),
|
||||||
|
// Nur für die Server-User-Prüfung gebraucht (siehe
|
||||||
|
// _pruefeServerUser) — ohne gespeicherte Zugangsdaten bleibt sie ein
|
||||||
|
// Gast.
|
||||||
|
ChangeNotifierProvider<DownloadService>(
|
||||||
|
create: (_) => DownloadService(
|
||||||
|
db: db, navidrome: navidrome ?? NavidromeService()),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: const MaterialApp(home: Scaffold(body: YoutubeSearchScreen())),
|
child: const MaterialApp(home: Scaffold(body: YoutubeSearchScreen())),
|
||||||
);
|
);
|
||||||
@@ -56,6 +67,15 @@ void main() {
|
|||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
setUp(() => SharedPreferences.setMockInitialValues({}));
|
setUp(() => SharedPreferences.setMockInitialValues({}));
|
||||||
|
|
||||||
|
// _pruefeServerUser prüft per NavidromeService.ladeGespeicherteZugangs-
|
||||||
|
// daten(), ob ein Server-User verbunden ist — ohne diese Antwort bleibt
|
||||||
|
// der Aufruf im Test unbeantwortet hängen.
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||||
|
.setMockMethodCallHandler(
|
||||||
|
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
||||||
|
(call) async => null,
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('ohne Anmeldung erscheint der Hinweis auf die Baka-Anmeldung',
|
testWidgets('ohne Anmeldung erscheint der Hinweis auf die Baka-Anmeldung',
|
||||||
(tester) async {
|
(tester) async {
|
||||||
final db = MeloDb(NativeDatabase.memory());
|
final db = MeloDb(NativeDatabase.memory());
|
||||||
@@ -75,6 +95,50 @@ void main() {
|
|||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Server-User sieht keinen Anmelde-Hinweis', (tester) async {
|
||||||
|
final db = MeloDb(NativeDatabase.memory());
|
||||||
|
final auth = BakaAuth(
|
||||||
|
client: MockClient(
|
||||||
|
(_) async => http.Response(jsonEncode({'token': 'jwt'}), 200)),
|
||||||
|
speicher: _MemorySpeicher(),
|
||||||
|
);
|
||||||
|
final search = YtSearchService(auth: auth);
|
||||||
|
final download = YtDownloadService(auth: auth);
|
||||||
|
final nav = NavidromeService()
|
||||||
|
..setCredentials('https://musik.baka-net.de', 'Dustin', 'geheim');
|
||||||
|
|
||||||
|
await tester.pumpWidget(_wrap(
|
||||||
|
db: db, auth: auth, search: search, download: download,
|
||||||
|
navidrome: nav));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.textContaining('Baka-Anmeldung'), findsNothing);
|
||||||
|
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Server-User bekommt bei fehlgeschlagener Auto-Anmeldung wieder '
|
||||||
|
'den Anmelde-Hinweis', (tester) async {
|
||||||
|
final db = MeloDb(NativeDatabase.memory());
|
||||||
|
final auth = BakaAuth(
|
||||||
|
client: MockClient((_) async => http.Response('', 500)),
|
||||||
|
speicher: _MemorySpeicher());
|
||||||
|
final search = YtSearchService(auth: auth);
|
||||||
|
final download = YtDownloadService(auth: auth);
|
||||||
|
final nav = NavidromeService()
|
||||||
|
..setCredentials('https://musik.baka-net.de', 'Dustin', 'falsch');
|
||||||
|
|
||||||
|
await tester.pumpWidget(_wrap(
|
||||||
|
db: db, auth: auth, search: search, download: download,
|
||||||
|
navidrome: nav));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.textContaining('Baka-Anmeldung'), findsOneWidget);
|
||||||
|
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Suche zeigt Titel und Dauer der Treffer', (tester) async {
|
testWidgets('Suche zeigt Titel und Dauer der Treffer', (tester) async {
|
||||||
final db = MeloDb(NativeDatabase.memory());
|
final db = MeloDb(NativeDatabase.memory());
|
||||||
final auth = await _angemeldeteAuth();
|
final auth = await _angemeldeteAuth();
|
||||||
|
|||||||
@@ -85,6 +85,50 @@ void main() {
|
|||||||
expect(auth.benutzer, 'Baka');
|
expect(auth.benutzer, 'Baka');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('autoAnmelden meldet mit den übergebenen Zugangsdaten an', () async {
|
||||||
|
late String gesendet;
|
||||||
|
final client = MockClient((req) async {
|
||||||
|
gesendet = req.body;
|
||||||
|
return http.Response(jsonEncode({'token': 'jwt-server'}), 200);
|
||||||
|
});
|
||||||
|
final auth = BakaAuth(client: client, speicher: _MemorySpeicher());
|
||||||
|
|
||||||
|
await auth.autoAnmelden('Tinker', 'geheim2');
|
||||||
|
|
||||||
|
expect(auth.istAngemeldet, isTrue);
|
||||||
|
expect(auth.benutzer, 'Tinker');
|
||||||
|
expect(jsonDecode(gesendet), {'username': 'Tinker', 'password': 'geheim2'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('autoAnmelden greift nicht ein, wenn schon angemeldet', () async {
|
||||||
|
var aufrufe = 0;
|
||||||
|
final client = MockClient((_) async {
|
||||||
|
aufrufe++;
|
||||||
|
return http.Response(jsonEncode({'token': 'jwt-abc'}), 200);
|
||||||
|
});
|
||||||
|
final auth = BakaAuth(client: client, speicher: _MemorySpeicher());
|
||||||
|
await auth.anmelden('Baka', 'geheim');
|
||||||
|
|
||||||
|
await auth.autoAnmelden('Tinker', 'geheim2');
|
||||||
|
|
||||||
|
expect(aufrufe, 1);
|
||||||
|
expect(auth.benutzer, 'Baka');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('autoAnmelden tut nichts ohne Zugangsdaten', () async {
|
||||||
|
var aufrufe = 0;
|
||||||
|
final client = MockClient((_) async {
|
||||||
|
aufrufe++;
|
||||||
|
return http.Response(jsonEncode({'token': 'jwt'}), 200);
|
||||||
|
});
|
||||||
|
final auth = BakaAuth(client: client, speicher: _MemorySpeicher());
|
||||||
|
|
||||||
|
await auth.autoAnmelden('', '');
|
||||||
|
|
||||||
|
expect(aufrufe, 0);
|
||||||
|
expect(auth.istAngemeldet, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
test('Abmelden löscht Token und Benutzer aus dem Speicher', () async {
|
test('Abmelden löscht Token und Benutzer aus dem Speicher', () async {
|
||||||
final speicher = _MemorySpeicher();
|
final speicher = _MemorySpeicher();
|
||||||
final auth = BakaAuth(
|
final auth = BakaAuth(
|
||||||
|
|||||||
Reference in New Issue
Block a user