YouTube-Gast-Zugang (App-Seite): 5 Downloads/Tag ohne Konto #4
@@ -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,6 +849,7 @@ class _YouTubeBereichState extends State<_YouTubeBereich> {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_KategorieFeld(controller: _kategorie, aktiv: !dienst.laeuft),
|
||||
if (!istGastModus)
|
||||
SwitchListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:melo/library/category_service.dart';
|
||||
import 'package:melo/library/database.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:provider/provider.dart';
|
||||
@@ -29,7 +30,9 @@ class _MemorySpeicher implements TokenSpeicher {
|
||||
Widget _wrap(BakaAuth auth,
|
||||
{required MeloDb db,
|
||||
required CategoryService categories,
|
||||
NavidromeService? navidrome}) {
|
||||
NavidromeService? navidrome,
|
||||
GastZugang? gast}) {
|
||||
final gastZugang = gast ?? GastZugang();
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider<MeloDb>.value(value: db),
|
||||
@@ -38,9 +41,11 @@ Widget _wrap(BakaAuth auth,
|
||||
// Aufräum-Timer von drift im Test-Rahmen hängen.
|
||||
ChangeNotifierProvider<CategoryService>.value(value: categories),
|
||||
ChangeNotifierProvider<BakaAuth>.value(value: auth),
|
||||
ChangeNotifierProvider<GastZugang>.value(value: gastZugang),
|
||||
ChangeNotifierProvider<YtDownloadService>(
|
||||
create: (_) => YtDownloadService(
|
||||
auth: auth,
|
||||
gast: gastZugang,
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
),
|
||||
),
|
||||
@@ -270,4 +275,63 @@ void main() {
|
||||
expect(gesendeterBenutzer, 'Tinker');
|
||||
expect(auth.istAngemeldet, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('Gast sieht zwei Buttons: Anmelden und Als Gast fortfahren',
|
||||
(tester) async {
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
|
||||
await tester.pumpWidget(_wrap(auth, db: db, categories: categories));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Beim Baka-Konto anmelden'), findsOneWidget);
|
||||
expect(
|
||||
find.text('Als Gast fortfahren (5 Downloads/Tag)'), findsOneWidget);
|
||||
expect(find.text('YouTube-Adresse'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Als Gast fortfahren holt einen Token und schaltet das Adressfeld frei',
|
||||
(tester) async {
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async =>
|
||||
http.Response(jsonEncode({'guest_token': 'g-1'}), 200)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(auth, db: db, categories: categories, gast: gast));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Als Gast fortfahren (5 Downloads/Tag)'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('YouTube-Adresse'), findsOneWidget);
|
||||
expect(find.text('Als Gast unterwegs (5 Downloads/Tag)'), findsOneWidget);
|
||||
expect(find.text('YouTube-Cookies des Servers verwenden'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Fehler beim Gast-Token wird angezeigt', (tester) async {
|
||||
final auth = BakaAuth(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher());
|
||||
final gast = GastZugang(
|
||||
client: MockClient((_) async => http.Response('', 500)),
|
||||
speicher: _MemorySpeicher(),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(auth, db: db, categories: categories, gast: gast));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Als Gast fortfahren (5 Downloads/Tag)'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Gast-Zugang fehlgeschlagen (500)'), findsOneWidget);
|
||||
expect(find.text('YouTube-Adresse'), findsNothing);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:melo/main.dart';
|
||||
import 'package:melo/player/audio_handler.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/melo_cloud_service.dart';
|
||||
import 'package:melo/services/navidrome_service.dart';
|
||||
import 'package:melo/services/offline_mode.dart';
|
||||
@@ -51,6 +52,7 @@ void main() {
|
||||
late YtSearchService ytSearch;
|
||||
late DownloadService downloads;
|
||||
late SyncService sync;
|
||||
late GastZugang gast;
|
||||
MeloAudioHandler? handler;
|
||||
|
||||
setUp(() {
|
||||
@@ -62,8 +64,9 @@ void main() {
|
||||
auth = BakaAuth(speicher: _MemorySpeicher());
|
||||
einstellungen = AppSettings();
|
||||
offline = OfflineMode();
|
||||
ytDownload = YtDownloadService(auth: auth);
|
||||
ytSearch = YtSearchService(auth: auth);
|
||||
gast = GastZugang();
|
||||
ytDownload = YtDownloadService(auth: auth, gast: gast);
|
||||
ytSearch = YtSearchService(auth: auth, gast: gast);
|
||||
downloads = DownloadService(db: db, navidrome: NavidromeService());
|
||||
sync = SyncService(db: db, cloud: MeloCloudService(auth: auth));
|
||||
});
|
||||
@@ -95,6 +98,7 @@ void main() {
|
||||
ChangeNotifierProvider<AppSettings>.value(value: einstellungen),
|
||||
ChangeNotifierProvider<OfflineMode>.value(value: offline),
|
||||
ChangeNotifierProvider<BakaAuth>.value(value: auth),
|
||||
ChangeNotifierProvider<GastZugang>.value(value: gast),
|
||||
ChangeNotifierProvider<YtDownloadService>.value(value: ytDownload),
|
||||
ChangeNotifierProvider<YtSearchService>.value(value: ytSearch),
|
||||
ChangeNotifierProvider<DownloadService>.value(value: downloads),
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:melo/main.dart';
|
||||
import 'package:melo/player/audio_handler.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/melo_cloud_service.dart';
|
||||
import 'package:melo/services/navidrome_service.dart';
|
||||
import 'package:melo/services/offline_mode.dart';
|
||||
@@ -50,6 +51,7 @@ void main() {
|
||||
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
||||
ChangeNotifierProvider<BakaAuth>(create: (_) => auth),
|
||||
ChangeNotifierProvider<GastZugang>(create: (_) => GastZugang()),
|
||||
ChangeNotifierProvider<YtDownloadService>(
|
||||
create: (_) => YtDownloadService(auth: auth)),
|
||||
ChangeNotifierProvider<YtSearchService>(
|
||||
|
||||
Reference in New Issue
Block a user