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

Echte Gäste sehen jetzt zwei Buttons: "Beim Baka-Konto anmelden"
und "Als Gast fortfahren (5 Downloads/Tag)". Im Gast-Modus wird
der Cookie-Schalter ausgeblendet (Server erzwingt cookies=false
für Gast-Zugriffe ohnehin) und ein Zähler "noch N von 5 heute"
gezeigt, sobald der Server ihn meldet.

_YouTubeBereich.build() beobachtet jetzt app-weit GastZugang; das
Qualitäts-Gate deckte auf, dass hauptmenue_test.dart und
home_shell_test.dart eigene Provider-Bäume ohne GastZugang bauen
(sie spiegeln main.dart nach, statt es zu importieren) — dort
GastZugang ergänzt, analog zum bestehenden Muster für die anderen
Dienste.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Hermes (Server)
2026-08-26 21:24:08 +02:00
co-authored by Claude Sonnet 5
parent 5347797292
commit cfebcc03b5
4 changed files with 136 additions and 14 deletions
+63 -11
View File
@@ -11,6 +11,7 @@ import '../player/audio_handler.dart';
import '../services/baka_auth.dart';
import '../services/media_store.dart';
import '../services/download_service.dart';
import '../services/gast_zugang.dart';
import '../services/navidrome_service.dart';
import '../services/server_neuheiten.dart';
import '../services/yt_download_service.dart';
@@ -615,6 +616,9 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
/// sichtbar, sonst käme so ein User nie mehr an den Dialog heran.
bool _autoLoginFehlgeschlagen = false;
/// Fehlertext, falls das Holen des Gast-Tokens scheitert.
String? _gastFehler;
@override
void initState() {
super.initState();
@@ -685,6 +689,14 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
);
}
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);
}
Future<void> _herunterladen() async {
final dienst = context.read<YtDownloadService>();
final lib = context.read<LibraryService>();
@@ -736,13 +748,17 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
@override
Widget build(BuildContext context) {
final auth = context.watch<BakaAuth>();
final gast = context.watch<GastZugang>();
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);
// Ein Gast-Token zählt ebenfalls als Zugang, nur mit Tages-Limit.
final zugriffOk = auth.istAngemeldet ||
(_serverUser && !_autoLoginFehlgeschlagen) ||
gast.hatToken;
final istGastModus = !_serverUser && !auth.istAngemeldet && gast.hatToken;
return ListView(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
@@ -769,8 +785,26 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
onPressed: _anmelden,
),
),
] else ...[
if (!_serverUser) ...[
const SizedBox(height: 8),
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) ...[
const SizedBox(height: 8),
Text(_gastFehler!,
style: const TextStyle(color: MeloTheme.red, fontSize: 13)),
],
],
] else ...[
if (!_serverUser && auth.istAngemeldet) ...[
Row(
children: [
const Icon(Icons.verified_user, size: 18, color: MeloTheme.text3),
@@ -787,6 +821,23 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
),
const SizedBox(height: 8),
],
if (istGastModus) ...[
Row(
children: [
const Icon(Icons.person_outline, size: 18, color: MeloTheme.text3),
const SizedBox(width: 8),
Expanded(
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),
),
),
],
),
const SizedBox(height: 8),
],
TextField(
controller: _url,
enabled: !dienst.laeuft,
@@ -798,14 +849,15 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
),
const SizedBox(height: 12),
_KategorieFeld(controller: _kategorie, aktiv: !dienst.laeuft),
SwitchListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: const Text('YouTube-Cookies des Servers verwenden'),
subtitle: const Text('Nötig für altersbeschränkte Videos'),
value: _cookies,
onChanged: dienst.laeuft ? null : _setzeCookies,
),
if (!istGastModus)
SwitchListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: const Text('YouTube-Cookies des Servers verwenden'),
subtitle: const Text('Nötig für altersbeschränkte Videos'),
value: _cookies,
onChanged: dienst.laeuft ? null : _setzeCookies,
),
if (_orte.length > 1) ...[
const SizedBox(height: 4),
DropdownButtonFormField<String>(