import 'package:cached_network_image/cached_network_image.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:onsolgo/core/constants.dart'; /// Series portfolio editor: cover (same view as chapter list context) and chapter removal. class ArtistSeriesManageScreen extends StatefulWidget { final DocumentSnapshot series; const ArtistSeriesManageScreen({super.key, required this.series}); @override State createState() => _ArtistSeriesManageScreenState(); } class _ArtistSeriesManageScreenState extends State { /// null | 'cover' | 'banner' String? _uploadingKind; Future _uploadSeriesCover() async { final picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.gallery, imageQuality: 70); if (image == null) return; setState(() => _uploadingKind = 'cover'); try { final ref = FirebaseStorage.instance.ref().child('series_covers').child('${widget.series.id}.jpg'); await ref.putData(await image.readAsBytes(), SettableMetadata(contentType: 'image/jpeg')); final url = await ref.getDownloadURL(); await widget.series.reference.update({'coverUrl': url}); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Cover updated'))); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Cover upload failed: $e'))); } } finally { if (mounted) setState(() => _uploadingKind = null); } } Future _uploadSeriesBanner() async { final picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.gallery, imageQuality: 72); if (image == null) return; setState(() => _uploadingKind = 'banner'); try { final ref = FirebaseStorage.instance.ref().child('series_banners').child('${widget.series.id}.jpg'); await ref.putData(await image.readAsBytes(), SettableMetadata(contentType: 'image/jpeg')); final url = await ref.getDownloadURL(); await widget.series.reference.update({'bannerUrl': url}); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Banner updated'))); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Banner upload failed: $e'))); } } finally { if (mounted) setState(() => _uploadingKind = null); } } Future _clearBanner() async { final ok = await showDialog( context: context, builder: (c) => AlertDialog( backgroundColor: Colors.grey[900], title: const Text('Remove banner?', style: TextStyle(color: Colors.white)), content: const Text( 'The series page will use the tall cover image at the top until you add a banner again.', style: TextStyle(color: Colors.white70), ), actions: [ TextButton(onPressed: () => Navigator.pop(c, false), child: const Text('Cancel')), TextButton( onPressed: () => Navigator.pop(c, true), child: const Text('Remove', style: TextStyle(color: Colors.redAccent)), ), ], ), ); if (ok != true || !mounted) return; try { await widget.series.reference.update({'bannerUrl': FieldValue.delete()}); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Banner removed'))); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Could not remove banner: $e'))); } } } Future _confirmDeleteChapter(DocumentSnapshot ch) async { final cd = ch.data() as Map; final numLabel = cd['chapterNumber']?.toString() ?? ch.id; final ok = await showDialog( context: context, builder: (c) => AlertDialog( backgroundColor: Colors.grey[900], title: const Text('Remove chapter?', style: TextStyle(color: Colors.white)), content: Text( 'Delete chapter $numLabel from Firestore? This cannot be undone.', style: const TextStyle(color: Colors.white70), ), actions: [ TextButton(onPressed: () => Navigator.pop(c, false), child: const Text('Cancel')), TextButton( onPressed: () => Navigator.pop(c, true), child: const Text('Delete', style: TextStyle(color: Colors.redAccent)), ), ], ), ); if (ok != true || !mounted) return; try { await ch.reference.delete(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Chapter $numLabel removed'))); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Could not delete: $e'))); } } } @override Widget build(BuildContext context) { final d = widget.series.data() as Map; final title = d['title']?.toString() ?? 'Series'; final cover = d['coverUrl']?.toString() ?? ''; final banner = d['bannerUrl']?.toString() ?? ''; final busy = _uploadingKind != null; return Scaffold( backgroundColor: Colors.black, appBar: AppBar( backgroundColor: Colors.amber[900], title: Text(title.toUpperCase(), style: const TextStyle(fontSize: 14)), ), body: Stack( children: [ StreamBuilder( stream: widget.series.reference.collection('chapters').orderBy('chapterNumber', descending: true).snapshots(), builder: (context, chSnap) { return CustomScrollView( slivers: [ SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( 'DETAIL PAGE BANNER', style: TextStyle(fontSize: 11, color: Colors.grey, fontWeight: FontWeight.bold, letterSpacing: 1.2), ), const SizedBox(height: 6), Text( 'Wide image at the top when readers open this series (library grid still uses the cover below).', style: TextStyle(fontSize: 11, color: Colors.grey[600], height: 1.3), ), const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: 16 / 9, child: banner.isEmpty ? Container( color: Colors.grey[850], alignment: Alignment.center, child: const Text('No banner yet', style: TextStyle(color: Colors.white38)), ) : CachedNetworkImage(imageUrl: banner, fit: BoxFit.cover, alignment: Alignment.center), ), ), const SizedBox(height: 10), Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: busy ? null : _uploadSeriesBanner, icon: const Icon(Icons.panorama_wide_angle_outlined, color: kOnsolGold), label: const Text('UPDATE BANNER', style: TextStyle(color: kOnsolGold)), style: OutlinedButton.styleFrom(side: const BorderSide(color: kOnsolGold)), ), ), if (banner.isNotEmpty) ...[ const SizedBox(width: 8), IconButton( tooltip: 'Remove banner', onPressed: busy ? null : _clearBanner, icon: const Icon(Icons.delete_outline, color: Colors.redAccent), ), ], ], ), const SizedBox(height: 24), const Text( 'LIBRARY COVER', style: TextStyle(fontSize: 11, color: Colors.grey, fontWeight: FontWeight.bold, letterSpacing: 1.2), ), const SizedBox(height: 10), ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: 3 / 4, child: cover.isEmpty ? Container( color: Colors.grey[850], alignment: Alignment.center, child: const Text('No cover', style: TextStyle(color: Colors.white38)), ) : CachedNetworkImage(imageUrl: cover, fit: BoxFit.cover, alignment: Alignment.topCenter), ), ), const SizedBox(height: 12), OutlinedButton.icon( onPressed: busy ? null : _uploadSeriesCover, icon: const Icon(Icons.photo_library_outlined, color: kOnsolGold), label: const Text('UPDATE SERIES COVER', style: TextStyle(color: kOnsolGold)), style: OutlinedButton.styleFrom(side: const BorderSide(color: kOnsolGold)), ), ], ), ), ), const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.fromLTRB(20, 0, 20, 8), child: Text('CHAPTERS', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), ), ), if (chSnap.hasError) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(24), child: Text('Error: ${chSnap.error}', style: const TextStyle(color: Colors.redAccent)), ), ) else if (!chSnap.hasData) const SliverFillRemaining( hasScrollBody: false, child: Center(child: CircularProgressIndicator(color: kOnsolGold)), ) else if (chSnap.data!.docs.isEmpty) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Center(child: Text('No chapters yet.', style: TextStyle(color: Colors.grey[500]))), ), ) else SliverPadding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, i) { final ch = chSnap.data!.docs[i]; final cd = ch.data() as Map; final thumb = cd['chapterCoverUrl']?.toString() ?? cover; return Card( color: Colors.grey[900], margin: const EdgeInsets.only(bottom: 10), child: ListTile( leading: ClipRRect( borderRadius: BorderRadius.circular(4), child: thumb.isEmpty ? const SizedBox(width: 50, height: 50, child: ColoredBox(color: Colors.white10)) : CachedNetworkImage(imageUrl: thumb, width: 50, height: 50, fit: BoxFit.cover), ), title: Text('Ch ${cd['chapterNumber']}', style: const TextStyle(color: Colors.white)), trailing: IconButton( icon: const Icon(Icons.delete_outline, color: Colors.redAccent), onPressed: () => _confirmDeleteChapter(ch), ), ), ); }, childCount: chSnap.data!.docs.length, ), ), ), ], ); }, ), if (_uploadingKind != null) const Positioned( left: 0, right: 0, bottom: 0, child: LinearProgressIndicator(minHeight: 3), ), ], ), ); } }