161 lines
8.0 KiB
Dart
161 lines
8.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
|
import 'package:onsolgo/core/constants.dart';
|
|
import 'package:onsolgo/screens/reader/reader_view.dart';
|
|
import 'package:onsolgo/widgets/energy_user_chip.dart';
|
|
import 'package:onsolgo/screens/auth/tier_comparison_screen.dart';
|
|
import 'package:share_plus/share_plus.dart';
|
|
|
|
class SeriesDetail extends StatefulWidget {
|
|
final DocumentSnapshot manga;
|
|
const SeriesDetail({super.key, required this.manga});
|
|
@override
|
|
State<SeriesDetail> createState() => _SeriesDetailState();
|
|
}
|
|
|
|
class _SeriesDetailState extends State<SeriesDetail> {
|
|
InterstitialAd? _interstitialAd;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
if (!kIsWeb) _loadInterstitial();
|
|
}
|
|
|
|
void _loadInterstitial() {
|
|
InterstitialAd.load(adUnitId: "ca-app-pub-3940256099942544/1033173712", request: const AdRequest(), adLoadCallback: InterstitialAdLoadCallback(onAdLoaded: (ad) => setState(() => _interstitialAd = ad), onAdFailedToLoad: (e) => debugPrint('$e')));
|
|
}
|
|
|
|
Future<void> _handleAccess(DocumentSnapshot ch) async {
|
|
final uid = FirebaseAuth.instance.currentUser?.uid; if (uid == null) return;
|
|
final userDoc = await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
final uData = userDoc.data() ?? <String, dynamic>{};
|
|
|
|
// ELITE BYPASS: Rank 5 or Paid Tier
|
|
if ((uData['tier'] ?? 'free') != 'free' || safeInt(uData['rankLevel']) == 5 || (uData['isVerified'] ?? false)) {
|
|
_openReader(ch); return;
|
|
}
|
|
|
|
int energy = safeInt(uData['energy'] ?? 2);
|
|
if (energy > 0) {
|
|
await userDoc.reference.update({'energy': energy - 1, 'lastEnergyRefill': FieldValue.serverTimestamp()});
|
|
if (_interstitialAd != null && !kIsWeb) {
|
|
_interstitialAd!.fullScreenContentCallback = FullScreenContentCallback(onAdDismissedFullScreenContent: (ad) { ad.dispose(); _openReader(ch); _loadInterstitial(); });
|
|
_interstitialAd!.show(); _interstitialAd = null;
|
|
} else { _openReader(ch); }
|
|
} else { _showExhausted(); }
|
|
}
|
|
|
|
void _openReader(DocumentSnapshot ch) { Navigator.push(context, MaterialPageRoute(builder: (c) => ReaderView(manga: widget.manga, chapter: ch))); }
|
|
void _showExhausted() { showDialog(context: context, builder: (ctx) => AlertDialog(backgroundColor: Colors.grey[900], shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15), side: const BorderSide(color: Colors.red)), title: const Text("ENERGY DEPLETED"), content: const Text("Recharge in 24 hours or upgrade to OGO+."), actions: [ElevatedButton(onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (c) => const TierComparisonScreen())), child: const Text("UPGRADE"))])); }
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final md = widget.manga.data() as Map<String, dynamic>;
|
|
final String cover = (md['coverUrl'] as String?)?.trim() ?? '';
|
|
final String banner = (md['bannerUrl'] as String?)?.trim() ?? '';
|
|
final bool hasBanner = banner.isNotEmpty;
|
|
final String heroUrl = hasBanner ? banner : cover;
|
|
final String uid = FirebaseAuth.instance.currentUser?.uid ?? "";
|
|
|
|
return Scaffold(
|
|
backgroundColor: Colors.black, extendBodyBehindAppBar: true,
|
|
appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0, actions: [IconButton(
|
|
icon: const Icon(Icons.share_outlined),
|
|
onPressed: () => SharePlus.instance.share(
|
|
ShareParams(text: 'Check out ${md['title']} on ONSOL-GO!\n\n$kOnsolAppWebUrl'),
|
|
),
|
|
)]),
|
|
body: SingleChildScrollView(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
Stack(children: [
|
|
CachedNetworkImage(
|
|
imageUrl: heroUrl,
|
|
width: double.infinity,
|
|
height: hasBanner ? 300 : 380,
|
|
fit: BoxFit.cover,
|
|
alignment: Alignment.topCenter,
|
|
placeholder: (context, url) => Container(
|
|
height: hasBanner ? 300 : 380,
|
|
color: Colors.grey[900],
|
|
),
|
|
),
|
|
Container(
|
|
height: hasBanner ? 301 : 381,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [Colors.transparent, Colors.black.withValues(alpha: 0.9), Colors.black],
|
|
),
|
|
),
|
|
),
|
|
]),
|
|
Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
if (hasBanner)
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(md['title'] ?? '', style: const TextStyle(fontSize: 26, fontWeight: FontWeight.bold)),
|
|
Text(md['author'] ?? '', style: const TextStyle(fontSize: 17, color: kOnsolGold)),
|
|
],
|
|
)
|
|
else ...[
|
|
Text(md['title'] ?? '', style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)),
|
|
Text(md['author'] ?? '', style: const TextStyle(fontSize: 18, color: kOnsolGold)),
|
|
],
|
|
const SizedBox(height: 12),
|
|
Align(alignment: Alignment.centerLeft, child: EnergyUserChip(uid: uid)),
|
|
const SizedBox(height: 16),
|
|
const Text("SYNOPSIS", style: TextStyle(fontSize: 10, color: Colors.grey, fontWeight: FontWeight.bold, letterSpacing: 2)),
|
|
Text(md['synopsis'] ?? "No transmission recorded.", style: const TextStyle(color: Colors.white70, height: 1.4)),
|
|
const SizedBox(height: 30),
|
|
const Text("CHAPTERS", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
|
StreamBuilder<QuerySnapshot>(
|
|
stream: widget.manga.reference.collection('chapters').orderBy('chapterNumber', descending: true).snapshots(),
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData) return const CircularProgressIndicator();
|
|
return ListView.builder(
|
|
padding: const EdgeInsets.only(top: 10), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: snapshot.data!.docs.length,
|
|
itemBuilder: (c, i) => _ChapterTile(ch: snapshot.data!.docs[i], manga: widget.manga, uid: uid, onTap: () => _handleAccess(snapshot.data!.docs[i])),
|
|
);
|
|
},
|
|
)
|
|
]),
|
|
),
|
|
])),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ChapterTile extends StatelessWidget {
|
|
final DocumentSnapshot ch; final DocumentSnapshot manga; final String uid; final VoidCallback onTap;
|
|
const _ChapterTile({required this.ch, required this.manga, required this.uid, required this.onTap});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cd = ch.data() as Map<String, dynamic>;
|
|
return StreamBuilder<DocumentSnapshot>(
|
|
stream: FirebaseFirestore.instance.collection('users').doc(uid).collection('progress').doc(manga.id).snapshots(),
|
|
builder: (context, prog) {
|
|
double p = (prog.hasData && prog.data!.exists && prog.data!['lastChapter'] == cd['chapterNumber']) ? prog.data!['percent'] : 0.0;
|
|
return Card(
|
|
color: Colors.grey[900], margin: const EdgeInsets.only(bottom: 12),
|
|
child: ListTile(
|
|
onTap: onTap,
|
|
leading: ClipRRect(borderRadius: BorderRadius.circular(4), child: CachedNetworkImage(imageUrl: cd['chapterCoverUrl'] ?? (manga.data() as Map)['coverUrl'], width: 50, height: 50, fit: BoxFit.cover)),
|
|
title: Text("Ch ${cd['chapterNumber']}"),
|
|
subtitle: LinearProgressIndicator(value: p, color: kOnsolGold, backgroundColor: Colors.white10, minHeight: 2),
|
|
trailing: const Icon(Icons.bolt, color: Colors.orangeAccent, size: 18),
|
|
),
|
|
);
|
|
}
|
|
);
|
|
}
|
|
} |