Merge branch '1-add-batch-feature' into 'master'

Resolve "Add batch feature"

Closes #1

See merge request moepoi/neonime-app!1
This commit is contained in:
Moe Poi ~ 2020-08-17 18:33:42 +00:00
commit b7d7f425fc
5 changed files with 384 additions and 7 deletions

View file

@ -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(),
));
},
),
],

View file

@ -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<Search> {
})
));
} 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(),

178
lib/pages/batch-detail.dart Normal file
View file

@ -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> asyncLoaderState =
new GlobalKey<AsyncLoaderState>();
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<CupertinoActionSheetAction> listButton =
List<CupertinoActionSheetAction>();
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,
);
});
},
),
),
),
],
),
);
}),
),
);
}
}

129
lib/pages/batch.dart Normal file
View file

@ -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<Batch>
with AutomaticKeepAliveClientMixin<Batch> {
final GlobalKey<AsyncLoaderState> asyncLoaderState =
new GlobalKey<AsyncLoaderState>();
ScrollController _scrollController = new ScrollController();
List listData;
Future<Null> _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;
}

60
lib/scrapper/batch.dart Normal file
View file

@ -0,0 +1,60 @@
import 'package:http/http.dart' as http;
import 'package:html/parser.dart';
Future<dynamic> getBatch(String page) async {
try {
List<String> images = <String>[];
List<String> links = <String>[];
List<String> titles = <String>[];
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 = <dynamic>[];
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<dynamic> getBatchDetail(String url) async {
try {
List<String> info = <String>[];
List<String> downloadUrls = <String>[];
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');
}
}