From 371291616cae04b3d787046402f7f751197f2dad Mon Sep 17 00:00:00 2001 From: Moe Date: Tue, 18 Aug 2020 01:33:04 +0700 Subject: [PATCH] Add batch feature --- lib/components/drawer.dart | 15 +-- lib/components/search.dart | 9 +- lib/pages/batch-detail.dart | 178 ++++++++++++++++++++++++++++++++++++ lib/pages/batch.dart | 129 ++++++++++++++++++++++++++ lib/scrapper/batch.dart | 60 ++++++++++++ 5 files changed, 384 insertions(+), 7 deletions(-) create mode 100644 lib/pages/batch-detail.dart create mode 100644 lib/pages/batch.dart create mode 100644 lib/scrapper/batch.dart diff --git a/lib/components/drawer.dart b/lib/components/drawer.dart index 42d9f49..682e92f 100644 --- a/lib/components/drawer.dart +++ b/lib/components/drawer.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:neonime_app/pages/about.dart'; +import 'package:neonime_app/pages/batch.dart'; class MainDrawer extends StatelessWidget { @override @@ -27,7 +28,11 @@ class MainDrawer extends StatelessWidget { ListTile( leading: Icon(Icons.library_books), title: Text('Batch'), - onTap: () {}, + onTap: () { + Navigator.push(context, MaterialPageRoute( + builder: (context) => Batch(), + )); + }, ), ListTile( leading: Icon(Icons.schedule), @@ -38,11 +43,9 @@ class MainDrawer extends StatelessWidget { leading: Icon(Icons.info), title: Text('About'), onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => About(), - )); + Navigator.push(context, MaterialPageRoute( + builder: (context) => About(), + )); }, ), ], diff --git a/lib/components/search.dart b/lib/components/search.dart index 698b17a..16d180d 100644 --- a/lib/components/search.dart +++ b/lib/components/search.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import 'package:neonime_app/pages/anime-detail.dart'; +import 'package:neonime_app/pages/batch-detail.dart'; import 'package:neonime_app/pages/episode-detail.dart'; import 'package:neonime_app/pages/movie-detail.dart'; import 'package:neonime_app/scrapper/search.dart'; @@ -57,7 +58,13 @@ class _SearchState extends State { }) )); } else if (data[index]['link'].contains('/batch/')) { - // OTW BIKIN + Navigator.push(context, MaterialPageRoute( + builder: (context) => BatchDetail(), + settings: RouteSettings(arguments: { + 'title': data[index]['title'], + 'url': data[index]['link'] + }) + )); } else { Navigator.push(context, MaterialPageRoute( builder: (context) => MovieDetail(), diff --git a/lib/pages/batch-detail.dart b/lib/pages/batch-detail.dart new file mode 100644 index 0000000..53f1cc1 --- /dev/null +++ b/lib/pages/batch-detail.dart @@ -0,0 +1,178 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:async_loader/async_loader.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:neonime_app/scrapper/batch.dart'; + +// ignore: must_be_immutable +class BatchDetail extends StatelessWidget { + final GlobalKey asyncLoaderState = + new GlobalKey(); + InAppWebViewController webView; + + @override + Widget build(BuildContext context) { + final argsData = ModalRoute.of(context).settings.arguments as Map; + final title = argsData['title']; + final postUrl = argsData['url']; + + return Scaffold( + appBar: AppBar( + title: Text(title), + ), + body: Center( + child: AsyncLoader( + key: asyncLoaderState, + initState: () async => await getBatchDetail(postUrl), + renderLoad: () => new CircularProgressIndicator(), + renderError: ([error]) { + print(error); + return Text(error.toString()); + }, + renderSuccess: ({data}) { + return Center( + child: ListView( + children: [ + Container( + height: 200, + child: CachedNetworkImage( + imageUrl: data['image'], + placeholder: (context, url) => CupertinoActivityIndicator(), + errorWidget: (context, url, error) => Image.asset('lib/assets/image-error.jpg'), + fadeOutDuration: Duration(milliseconds: 5), + imageBuilder: (context, imageProvider) => Container( + width: 200, + height: 80, + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover + ), + ), + ), + ), + ), + Card( + child: Container( + margin: EdgeInsets.all(10), + child: Text( + data['description'], + style: TextStyle(fontSize: 16), + ), + )), + Card( + child: Container( + margin: EdgeInsets.all(10), + child: Text( + data['info'], + style: TextStyle(fontSize: 16), + ), + )), + Card( + child: InkWell( + onTap: () {}, + child: Row( + children: [ + Flexible( + flex: 2, + child: Container( + margin: EdgeInsets.all(8), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: CachedNetworkImage( + imageUrl: data['image'], + placeholder: (context, url) => CupertinoActivityIndicator(), + errorWidget: (context, url, error) => Image.asset('lib/assets/image-error.jpg'), + fadeOutDuration: Duration(milliseconds: 5), + imageBuilder: (context, imageProvider) => Container( + width: 120, + height: 130, + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover + ), + ), + ), + ), + ), + ), + ), + Flexible( + flex: 5, + child: Container( + margin: EdgeInsets.all(5), + child: Text( + data['title'], + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.w400), + ), + ), + ) + ], + ), + ), + ), + Center( + child: Container( + margin: EdgeInsets.all(10), + child: CupertinoButton.filled( + child: Text('Click to Download'), + onPressed: () { + List listButton = + List(); + var counter = 0; + data['download_url'].forEach((link) { + counter++; + listButton.add(CupertinoActionSheetAction( + child: Text( + 'Download #${counter.toString()}', + ), + onPressed: () { + showCupertinoDialog( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: Text('Your Download Link'), + content: Text(link), + actions: [ + CupertinoDialogAction( + child: Text('Go'), + onPressed: () async { + await InAppBrowser + .openWithSystemBrowser( + url: link); + }, + ) + ], + ); + }, + barrierDismissible: true); + }, + )); + }); + if (listButton.length > 5) { + listButton.removeRange(5, listButton.length); + } + showCupertinoModalPopup( + context: context, + builder: (context) { + return CupertinoActionSheet( + title: Text('Download Link'), + message: Text('Click the button to download'), + actions: listButton, + ); + }); + }, + ), + ), + ), + ], + ), + ); + }), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/batch.dart b/lib/pages/batch.dart new file mode 100644 index 0000000..7ef0842 --- /dev/null +++ b/lib/pages/batch.dart @@ -0,0 +1,129 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:async_loader/async_loader.dart'; +import 'package:neonime_app/scrapper/batch.dart'; +import 'batch-detail.dart'; + +class Batch extends StatefulWidget { + @override + _BatchState createState() => _BatchState(); +} + +class _BatchState extends State + with AutomaticKeepAliveClientMixin { + final GlobalKey asyncLoaderState = + new GlobalKey(); + ScrollController _scrollController = new ScrollController(); + List listData; + + Future _handleRefresh() async { + asyncLoaderState.currentState.reloadState(); + return null; + } + + @override + void initState() { + super.initState(); + _scrollController.addListener(() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + _getMoreData(); + } + }); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _getMoreData() async { + final nextPage = (listData.length / 15) + 1; + List newData = await getBatch(nextPage.round().toString()).then((x) { + return x; + }); + setState(() { + listData.addAll(newData); + }); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Scaffold( + appBar: AppBar(title: Text('Batch'),), + body: Center( + child: RefreshIndicator( + onRefresh: () => _handleRefresh(), + child: AsyncLoader( + key: asyncLoaderState, + initState: () async => await getBatch('1'), + renderLoad: () => new CircularProgressIndicator(), + renderError: ([error]) { + print(error); + return Text(error.toString()); + }, + renderSuccess: ({data}) { + listData = data; + return ListView.builder( + controller: _scrollController, + itemCount: data.length, + itemBuilder: (context, index) { + return Card( + margin: EdgeInsets.all(10), + child: InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BatchDetail(), + settings: RouteSettings(arguments: { + 'title': listData[index]['title'], + 'url': listData[index]['link'] + }))); + }, + child: Column( + children: [ + Container( + child: CachedNetworkImage( + imageUrl: listData[index]['image'], + placeholder: (context, url) => CupertinoActivityIndicator(), + errorWidget: (context, url, error) => Image.asset('lib/assets/image-error.jpg'), + fadeOutDuration: Duration(milliseconds: 5), + imageBuilder: (context, imageProvider) => Container( + width: 400, + height: 210, + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover + ), + ), + ), + ) + ), + Container( + margin: EdgeInsets.all(10), + child: Text( + listData[index]['title'], + style: TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ), + ) + ], + ), + ), + ); + }, + ); + }), + ), + ), + ); + } + + @override + bool get wantKeepAlive => true; +} \ No newline at end of file diff --git a/lib/scrapper/batch.dart b/lib/scrapper/batch.dart new file mode 100644 index 0000000..bf19e1d --- /dev/null +++ b/lib/scrapper/batch.dart @@ -0,0 +1,60 @@ +import 'package:http/http.dart' as http; +import 'package:html/parser.dart'; + +Future getBatch(String page) async { + try { + List images = []; + List links = []; + List titles = []; + final response = await http.get('https://neonime.moe/batch/page/$page/'); + var document = parse(response.body); + var items = document.getElementsByClassName('item'); + items.forEach((item) { + images.add(item.getElementsByTagName('img')[0].attributes['data-wpfc-original-src']); + links.add(item.getElementsByTagName('a')[0].attributes['href']); + titles.add(item.getElementsByClassName('title')[0].text); + }); + var data = []; + for (var i = 0; i < links.length; i++) { + data.add({ + 'image': images[i].replaceAll(new RegExp(r'\/w(\d\d\d)\/'), '/original/'), + 'link': links[i], + 'title': titles[i] + }); + } + return data; + } catch (e) { + print(e); + throw FormatException('ntah lah'); + } +} + +Future getBatchDetail(String url) async { + try { + List info = []; + List downloadUrls = []; + final response = await http.get(url); + var document = parse(response.body); + var contentBox = document.getElementsByClassName('entry-content')[0]; + var title = contentBox.getElementsByTagName('h1')[0].text; + var image = contentBox.getElementsByTagName('img')[0].attributes['data-wpfc-original-src']; + var description = contentBox.getElementsByTagName('p'); + contentBox.getElementsByClassName('spaceit').forEach((x) { + info.add(x.text); + }); + contentBox.getElementsByClassName('smokeurl')[0].getElementsByTagName('a').forEach((x) { + downloadUrls.add(x.attributes['href']); + }); + final data = { + 'title': title, + 'description': description[2].text.length > 100 ? description[2].text : description[3].text, + 'info': info.join('\n\n'), + 'image': image.replaceAll(new RegExp(r'\/w(\d\d\d)\/'), '/original/'), + 'download_url': downloadUrls + }; + return data; + } catch (e) { + print(e); + throw FormatException('ntah lah'); + } +} \ No newline at end of file