YT-Suche-Tab: Gast-Zugang mit bewusster Wahl + Tages-Zähler

Analog zum Download-Tab: echte Gäste sehen zusätzlich zum
bestehenden Hinweis den Button "Als Gast fortfahren (5
Downloads/Tag)"; im Gast-Modus ersetzt ein Zähler den Hinweis.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Hermes (Server)
2026-08-26 21:37:08 +02:00
co-authored by Claude Sonnet 5
parent 81ec1e232b
commit aecb8e3364
2 changed files with 104 additions and 6 deletions
+55 -6
View File
@@ -7,6 +7,7 @@ import '../library/database.dart';
import '../library/library_service.dart';
import '../services/baka_auth.dart';
import '../services/download_service.dart';
import '../services/gast_zugang.dart';
import '../services/media_store.dart';
import '../services/yt_download_service.dart';
import '../services/yt_search_service.dart';
@@ -34,6 +35,9 @@ class _YoutubeSearchScreenState extends State<YoutubeSearchScreen> {
/// Hinweis auf die manuelle Anmeldung (siehe Download-Tab) sichtbar.
bool _autoLoginFehlgeschlagen = false;
/// Fehlertext, falls das Holen des Gast-Tokens scheitert.
String? _gastFehler;
@override
void initState() {
super.initState();
@@ -51,6 +55,14 @@ class _YoutubeSearchScreenState extends State<YoutubeSearchScreen> {
});
}
Future<void> _alsGastFortfahren() async {
final gast = context.read<GastZugang>();
setState(() => _gastFehler = null);
final fehler = await gast.holeToken();
if (!mounted) return;
if (fehler != null) setState(() => _gastFehler = fehler);
}
@override
void dispose() {
_query.dispose();
@@ -67,9 +79,12 @@ class _YoutubeSearchScreenState extends State<YoutubeSearchScreen> {
@override
Widget build(BuildContext context) {
final auth = context.watch<BakaAuth>();
final gast = context.watch<GastZugang>();
final suche = context.watch<YtSearchService>();
final zugriffOk =
auth.istAngemeldet || (_serverUser && !_autoLoginFehlgeschlagen);
final zugriffOk = auth.istAngemeldet ||
(_serverUser && !_autoLoginFehlgeschlagen) ||
gast.hatToken;
final istGastModus = !_serverUser && !auth.istAngemeldet && gast.hatToken;
return SafeArea(
child: Column(
@@ -97,13 +112,47 @@ class _YoutubeSearchScreenState extends State<YoutubeSearchScreen> {
],
),
),
if (!zugriffOk)
const Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
if (!zugriffOk) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Text(
'Die Suche läuft über den Baka-Server. Dafür brauchst du '
'deine Baka-Anmeldung (siehe Download-Tab).',
style: TextStyle(color: MeloTheme.text2, fontSize: 13),
style: const TextStyle(color: MeloTheme.text2, fontSize: 13),
),
),
if (!_serverUser) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
width: double.infinity,
height: MeloTheme.minTouchTarget + 4,
child: OutlinedButton.icon(
icon: const Icon(Icons.person_outline),
label: const Text('Als Gast fortfahren (5 Downloads/Tag)',
style: TextStyle(fontSize: 16)),
onPressed: _alsGastFortfahren,
),
),
),
if (_gastFehler != null)
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Text(_gastFehler!,
style:
const TextStyle(color: MeloTheme.red, fontSize: 13)),
),
],
],
if (istGastModus)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Text(
gast.verbleibend == null
? 'Als Gast unterwegs (5 Downloads/Tag)'
: 'Als Gast unterwegs — noch ${gast.verbleibend} von 5 heute',
style: const TextStyle(color: MeloTheme.text2, fontSize: 13),
),
),
if (suche.laeuft) const LinearProgressIndicator(),
@@ -11,6 +11,7 @@ import 'package:melo/library/database.dart';
import 'package:melo/library/library_service.dart';
import 'package:melo/services/baka_auth.dart';
import 'package:melo/services/download_service.dart';
import 'package:melo/services/gast_zugang.dart';
import 'package:melo/services/navidrome_service.dart';
import 'package:melo/services/yt_download_service.dart';
import 'package:melo/services/yt_search_service.dart';
@@ -43,12 +44,14 @@ Widget _wrap({
required YtSearchService search,
required YtDownloadService download,
NavidromeService? navidrome,
GastZugang? gast,
}) {
return MultiProvider(
providers: [
Provider<MeloDb>.value(value: db),
ChangeNotifierProvider<LibraryService>.value(value: LibraryService(db)),
ChangeNotifierProvider<BakaAuth>.value(value: auth),
ChangeNotifierProvider<GastZugang>.value(value: gast ?? GastZugang()),
ChangeNotifierProvider<YtSearchService>.value(value: search),
ChangeNotifierProvider<YtDownloadService>.value(value: download),
// Nur für die Server-User-Prüfung gebraucht (siehe
@@ -182,4 +185,50 @@ void main() {
// _YouTubeBereich (downloads_screen.dart) keinen eigenen Widget-Test —
// die Download-Logik selbst ist über yt_download_test.dart ausführlich
// abgedeckt.
testWidgets('Gast sieht den Button "Als Gast fortfahren"', (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);
await tester.pumpWidget(_wrap(
db: db, auth: auth, search: search, download: download));
await tester.pumpAndSettle();
expect(
find.text('Als Gast fortfahren (5 Downloads/Tag)'), findsOneWidget);
await db.close();
});
testWidgets(
'Als Gast fortfahren blendet den Baka-Hinweis aus und zeigt den Zähler',
(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 gast = GastZugang(
client: MockClient((_) async =>
http.Response(jsonEncode({'guest_token': 'g-1'}), 200)),
speicher: _MemorySpeicher(),
);
await tester.pumpWidget(_wrap(
db: db, auth: auth, search: search, download: download, gast: gast));
await tester.pumpAndSettle();
await tester.tap(find.text('Als Gast fortfahren (5 Downloads/Tag)'));
await tester.pumpAndSettle();
expect(find.textContaining('Baka-Anmeldung'), findsNothing);
expect(find.text('Als Gast unterwegs (5 Downloads/Tag)'), findsOneWidget);
await db.close();
});
}