mirror of
https://github.com/nrop19/weiman_app.git
synced 2025-08-02 23:05:48 +08:00
v1.0.8
This commit is contained in:
parent
038f6d5eaf
commit
f68617e44b
@ -15,15 +15,12 @@ class BookState extends State<ActivityBook> {
|
||||
ScrollController _scrollController;
|
||||
|
||||
bool _reverse = false;
|
||||
bool isFavorite = false;
|
||||
bool isLoading = true, isSuccess = false;
|
||||
Book book;
|
||||
List<Chapter> chapters = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
isFavorite = widget.book.isFavorite();
|
||||
book = widget.book;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_refresh.currentState
|
||||
.show(notificationDragOffset: SliverPullToRefreshHeader.height);
|
||||
@ -38,47 +35,36 @@ class BookState extends State<ActivityBook> {
|
||||
}
|
||||
|
||||
Future<bool> loadBook() async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
isSuccess = false;
|
||||
});
|
||||
try {
|
||||
book = await UserAgentClient.instance
|
||||
.getBook(aid: widget.book.aid)
|
||||
.timeout(Duration(seconds: 5));
|
||||
book.history = Data.getHistories()[book.aid]?.history;
|
||||
chapters
|
||||
..clear()
|
||||
..addAll(book.chapters);
|
||||
if (_reverse) chapters = chapters.reversed.toList();
|
||||
|
||||
/// 更新收藏列表里的漫画数据
|
||||
if (isFavorite) Data.addFavorite(book);
|
||||
|
||||
_scrollToRead();
|
||||
isLoading = false;
|
||||
isSuccess = true;
|
||||
} catch (e) {
|
||||
isLoading = false;
|
||||
isSuccess = false;
|
||||
return false;
|
||||
final cacheBook = await Book.loadBookCache(widget.book.aid);
|
||||
if (cacheBook == null) {
|
||||
print('没有缓存');
|
||||
try {
|
||||
await loadBookFromInternet();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
print('有缓存');
|
||||
book = cacheBook;
|
||||
updateBook();
|
||||
loadBookFromInternet().then((_) => updateBook());
|
||||
}
|
||||
print('刷新 $book');
|
||||
setState(() {});
|
||||
return true;
|
||||
}
|
||||
|
||||
void _scrollToRead() {
|
||||
if (book.history != null) {
|
||||
final history = book.chapters
|
||||
.firstWhere((chapter) => chapter.cid == book.history.cid);
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollController.animateTo(
|
||||
WidgetChapter.height * chapters.indexOf(history).toDouble(),
|
||||
duration: Duration(milliseconds: 500),
|
||||
curve: Curves.linear);
|
||||
});
|
||||
}
|
||||
Future<dynamic> loadBookFromInternet() async {
|
||||
final internetBook = await HttpHHMH39.instance
|
||||
.getBook(widget.book.aid)
|
||||
.timeout(Duration(seconds: 10));
|
||||
book = internetBook;
|
||||
if (book.isFavorite()) Data.addFavorite(book);
|
||||
book.saveBookCache();
|
||||
updateBook();
|
||||
}
|
||||
|
||||
void updateBook() {
|
||||
book.history = Data.getHistories()[book.aid]?.history;
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
_openChapter(Chapter chapter) {
|
||||
@ -88,60 +74,100 @@ class BookState extends State<ActivityBook> {
|
||||
});
|
||||
}
|
||||
|
||||
favoriteBook() {
|
||||
widget.book.favorite();
|
||||
isFavorite = !isFavorite;
|
||||
favoriteBook() async {
|
||||
final fav = Provider.of<FavoriteData>(context, listen: false);
|
||||
if (book.isFavorite()) {
|
||||
final isFavoriteBook = Data._quickIdList().contains(book.aid);
|
||||
if (isFavoriteBook) {
|
||||
final sure = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: Text('确认取消收藏?'),
|
||||
content: Text('删除这本藏书后,首页的快速导航也会删除这本藏书'),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: Text('确认'),
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
),
|
||||
FlatButton(
|
||||
child: Text('取消'),
|
||||
textColor: Colors.blue,
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
),
|
||||
],
|
||||
));
|
||||
if (!sure) return;
|
||||
}
|
||||
fav.remove(book);
|
||||
} else
|
||||
fav.add(book);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _sort() {
|
||||
setState(() {
|
||||
_reverse = !_reverse;
|
||||
chapters = chapters.reversed.toList();
|
||||
_scrollToRead();
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> chapterWidgets() {
|
||||
final book = this.book ?? widget.book;
|
||||
List<Widget> list = [];
|
||||
chapters.forEach((chapter) {
|
||||
final isRead = chapter.cid == book.history?.cid;
|
||||
list.add(WidgetChapter(
|
||||
chapter: chapter,
|
||||
onTap: _openChapter,
|
||||
read: isRead,
|
||||
));
|
||||
});
|
||||
List<Chapter> _sort() {
|
||||
final List<Chapter> list = List.from(book.chapters);
|
||||
if (_reverse) return list.reversed.toList();
|
||||
return list;
|
||||
}
|
||||
|
||||
Widget buildChapter(BuildContext context, int index) {
|
||||
final book = this.book ?? widget.book;
|
||||
final chapter = chapters[index];
|
||||
final isRead = chapter.cid == book.history?.cid;
|
||||
if (index < chapters.length - 1) {
|
||||
return DecoratedBox(
|
||||
decoration: _Main._border,
|
||||
child: WidgetChapter(
|
||||
IndexedWidgetBuilder buildChapters(List<Chapter> chapters) {
|
||||
IndexedWidgetBuilder builder = (BuildContext context, int index) {
|
||||
final chapter = chapters[index];
|
||||
Widget child;
|
||||
if (chapter.avatar == null) {
|
||||
child = ListTile(
|
||||
leading: Text('[已看]', style: TextStyle(color: Colors.orange)),
|
||||
title: Text(chapter.cname),
|
||||
onTap: () {},
|
||||
);
|
||||
} else {
|
||||
child = WidgetChapter(
|
||||
chapter: chapter,
|
||||
onTap: _openChapter,
|
||||
read: isRead,
|
||||
),
|
||||
);
|
||||
}
|
||||
return WidgetChapter(
|
||||
chapter: chapter,
|
||||
onTap: _openChapter,
|
||||
read: isRead,
|
||||
);
|
||||
read: chapter.cid == book.history?.cid,
|
||||
);
|
||||
}
|
||||
if (index < chapters.length - 1)
|
||||
child = DecoratedBox(
|
||||
decoration: _Main._border,
|
||||
child: child,
|
||||
);
|
||||
return child;
|
||||
};
|
||||
return builder;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isFavorite = book.isFavorite();
|
||||
Color color = isFavorite ? Colors.red : Colors.white;
|
||||
IconData icon = isFavorite ? Icons.favorite : Icons.favorite_border;
|
||||
final book = this.book ?? widget.book;
|
||||
final List<Chapter> chapters = _sort();
|
||||
final history = <Widget>[];
|
||||
if (book.history != null && book.chapters.length > 0) {
|
||||
final chapter = book.chapters
|
||||
.firstWhere((chapter) => chapter.cid == book.history.cid);
|
||||
history.add(ListTile(title: Text('阅读历史')));
|
||||
history.add(WidgetChapter(
|
||||
chapter: chapter,
|
||||
onTap: _openChapter,
|
||||
read: true,
|
||||
));
|
||||
history.add(ListTile(title: Text('下一章')));
|
||||
final nextIndex = book.chapters.indexOf(chapter) + 1;
|
||||
if (nextIndex < book.chapterCount) {
|
||||
history.add(WidgetChapter(
|
||||
chapter: book.chapters[nextIndex],
|
||||
onTap: _openChapter,
|
||||
read: false,
|
||||
));
|
||||
} else {
|
||||
history.add(ListTile(subtitle: Text('没有了')));
|
||||
}
|
||||
history.add(SizedBox(height: 20));
|
||||
}
|
||||
history.add(ListTile(title: Text('章节列表')));
|
||||
|
||||
return Scaffold(
|
||||
body: PullToRefreshNotification(
|
||||
key: _refresh,
|
||||
@ -153,11 +179,16 @@ class BookState extends State<ActivityBook> {
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
pinned: true,
|
||||
title: Text(widget.book.name),
|
||||
title: Text(book.name),
|
||||
expandedHeight: 200,
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: _sort,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_reverse = !_reverse;
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
icon: Icon(_reverse
|
||||
? FontAwesomeIcons.sortNumericDown
|
||||
: FontAwesomeIcons.sortNumericDownAlt)),
|
||||
@ -175,8 +206,12 @@ class BookState extends State<ActivityBook> {
|
||||
height: 160,
|
||||
child: Hero(
|
||||
tag: widget.heroTag,
|
||||
child:
|
||||
Image(image: NetworkImageSSL(widget.book.avatar)),
|
||||
child: Image(
|
||||
image: ExtendedNetworkImageProvider(
|
||||
book.avatar,
|
||||
cache: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@ -211,10 +246,16 @@ class BookState extends State<ActivityBook> {
|
||||
onTap: () => _refresh.currentState.show(
|
||||
notificationDragOffset: SliverPullToRefreshHeader.height),
|
||||
)),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: history,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
buildChapter,
|
||||
childCount: book.chapters.length,
|
||||
buildChapters(chapters),
|
||||
childCount: chapters.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -19,6 +19,7 @@ class ChapterState extends State<ActivityChapter> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
saveHistory(widget.chapter);
|
||||
_pageController = PageController(
|
||||
keepPage: false,
|
||||
initialPage: widget.book.chapters.indexOf(widget.chapter));
|
||||
@ -30,6 +31,15 @@ class ChapterState extends State<ActivityChapter> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void pageChanged(int page) {
|
||||
saveHistory(widget.book.chapters[page]);
|
||||
widget.book.saveBookCache();
|
||||
}
|
||||
|
||||
void saveHistory(Chapter chapter) {
|
||||
Data.addHistory(widget.book, chapter);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -41,23 +51,25 @@ class ChapterState extends State<ActivityChapter> {
|
||||
},
|
||||
),
|
||||
body: PageView.builder(
|
||||
physics: AlwaysScrollableClampingScrollPhysics(),
|
||||
controller: _pageController,
|
||||
itemCount: widget.book.chapters.length,
|
||||
itemBuilder: (ctx, index) {
|
||||
return ChapterContentView(
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
_scaffoldKey.currentState.openEndDrawer();
|
||||
},
|
||||
),
|
||||
],
|
||||
book: widget.book,
|
||||
chapter: widget.book.chapters[index],
|
||||
);
|
||||
}),
|
||||
physics: AlwaysScrollableClampingScrollPhysics(),
|
||||
controller: _pageController,
|
||||
itemCount: widget.book.chapters.length,
|
||||
onPageChanged: pageChanged,
|
||||
itemBuilder: (ctx, index) {
|
||||
return ChapterContentView(
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
_scaffoldKey.currentState.openEndDrawer();
|
||||
},
|
||||
),
|
||||
],
|
||||
book: widget.book,
|
||||
chapter: widget.book.chapters[index],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -170,19 +182,19 @@ class _ChapterContentView extends State<ChapterContentView> {
|
||||
|
||||
Future<bool> fetchImages() async {
|
||||
print('fetchImages');
|
||||
if (mounted) setState(() {});
|
||||
loading = true;
|
||||
images.clear();
|
||||
if (mounted) setState(() {});
|
||||
final _images = Chapter.fromCache(widget.book, widget.chapter);
|
||||
if (_images != null) {
|
||||
print('章节 有缓存');
|
||||
images.addAll(_images);
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
images.addAll(await UserAgentClient.instance
|
||||
.getImages(aid: widget.book.aid, cid: widget.chapter.cid)
|
||||
.timeout(Duration(seconds: 5)));
|
||||
if (images.length < 5) {
|
||||
// print('图片 前:' + images.toString());
|
||||
final list =
|
||||
await checkImage(images.last).timeout(Duration(seconds: 15));
|
||||
images.addAll(list);
|
||||
}
|
||||
images.addAll(await HttpHHMH39.instance
|
||||
.getChapterImages(widget.book, widget.chapter)
|
||||
.timeout(Duration(seconds: 10)));
|
||||
} catch (e) {
|
||||
print('错误 $e');
|
||||
showToastWidget(
|
||||
@ -206,6 +218,7 @@ class _ChapterContentView extends State<ChapterContentView> {
|
||||
}
|
||||
loading = false;
|
||||
// print('所有图片:' + images.toString());
|
||||
Chapter.saveCache(widget.book, widget.chapter, images);
|
||||
if (mounted) setState(() {});
|
||||
return true;
|
||||
}
|
||||
@ -264,8 +277,9 @@ class _ChapterContentView extends State<ChapterContentView> {
|
||||
],
|
||||
),
|
||||
),
|
||||
content: ExtendedImage(
|
||||
image: NetworkImageSSL(images[i]),
|
||||
content: ExtendedImage.network(
|
||||
images[i],
|
||||
cache: true,
|
||||
enableLoadState: true,
|
||||
enableMemoryCache: true,
|
||||
fit: BoxFit.fitWidth,
|
||||
@ -303,22 +317,6 @@ class _ChapterContentView extends State<ChapterContentView> {
|
||||
}
|
||||
},
|
||||
),
|
||||
// content: Image(
|
||||
// image: NetworkImageSSL(images[i]),
|
||||
// loadingBuilder: (_, child, loadingProgress) {
|
||||
// if (loadingProgress == null) return child;
|
||||
// return SizedBox(
|
||||
// height: 400,
|
||||
// child: Center(
|
||||
// child: CircularProgressIndicator(
|
||||
// value: loadingProgress.expectedTotalBytes != null
|
||||
// ? loadingProgress.cumulativeBytesLoaded /
|
||||
// loadingProgress.expectedTotalBytes
|
||||
// : null,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
);
|
||||
},
|
||||
childCount: images.length,
|
||||
@ -349,10 +347,8 @@ Future<List<String>> checkImage(String last) async {
|
||||
file2 = getFileName(name: fileName, divider: '_', plus: plus + 1);
|
||||
var url1 = '$a/$b/$file1.$fileFormat', url2 = '$a/$b/$file2.$fileFormat';
|
||||
// print('正在测试:\n' + url1 + '\n' + url2);
|
||||
final res = await Future.wait([
|
||||
UserAgentClient.instance.head(url1),
|
||||
UserAgentClient.instance.head(url2)
|
||||
]);
|
||||
final res = await Future.wait(
|
||||
[HttpHHMH39.instance.head(url1), HttpHHMH39.instance.head(url2)]);
|
||||
if (res[0].statusCode != 200) break;
|
||||
list.add(url1);
|
||||
if (res[1].statusCode != 200) {
|
||||
|
@ -17,7 +17,7 @@ final titleTextStyle = TextStyle(fontSize: 14, color: Colors.blue),
|
||||
|
||||
class _State extends State<ActivityCheckData> {
|
||||
CheckState firstState;
|
||||
int firstLength;
|
||||
int firstLength = 0;
|
||||
final TextSpan secondResults = TextSpan();
|
||||
TextEditingController _outputController, _inputController;
|
||||
|
||||
|
@ -26,10 +26,11 @@ class HomeState extends State<ActivityHome> {
|
||||
/// 提前检查一次藏书的更新情况
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||||
autoSwitchTheme();
|
||||
_FavoriteList.getBooks();
|
||||
await _FavoriteList.checkNews();
|
||||
final updated = _FavoriteList.hasNews.values
|
||||
.where((int updatedChapters) => updatedChapters > 0)
|
||||
FavoriteData favData = Provider.of<FavoriteData>(context,listen: false);
|
||||
await favData.loadBooksList();
|
||||
await favData.checkNews(Provider.of<SettingData>(context, listen: false).autoCheck);
|
||||
final updated = favData.hasNews.values
|
||||
.where((int count) => count > 0)
|
||||
.length;
|
||||
if (updated > 0)
|
||||
showToast(
|
||||
@ -59,11 +60,15 @@ class HomeState extends State<ActivityHome> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
settings: RouteSettings(name: '/activity_recommend/'),
|
||||
settings: RouteSettings(name: '/activity_recommend'),
|
||||
builder: (_) => ActivityRank(),
|
||||
));
|
||||
}
|
||||
|
||||
void gotoPatreon() {
|
||||
launch('https://www.patreon.com/nrop19');
|
||||
}
|
||||
|
||||
bool isEdit = false;
|
||||
|
||||
void _draggableModeChanged(bool mode) {
|
||||
@ -104,6 +109,18 @@ class HomeState extends State<ActivityHome> {
|
||||
),
|
||||
SizedBox(width: 20),
|
||||
|
||||
/// 设置界面
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
settings: RouteSettings(name: '/activity_setting'),
|
||||
builder: (_) => ActivitySetting()));
|
||||
},
|
||||
icon: Icon(FontAwesomeIcons.cog),
|
||||
),
|
||||
|
||||
/// 收藏列表
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
@ -138,110 +155,125 @@ class HomeState extends State<ActivityHome> {
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.only(left: 40, right: 40),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: OutlineButton(
|
||||
onPressed: gotoSearch,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.search,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Text(
|
||||
'搜索漫画',
|
||||
style: TextStyle(color: Colors.blue),
|
||||
)
|
||||
],
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.only(left: 40, right: 40),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: OutlineButton(
|
||||
onPressed: gotoSearch,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.search,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Text(
|
||||
'搜索漫画',
|
||||
style: TextStyle(color: Colors.blue),
|
||||
)
|
||||
],
|
||||
),
|
||||
borderSide: BorderSide(color: Colors.blue, width: 2),
|
||||
shape: StadiumBorder(),
|
||||
),
|
||||
borderSide: BorderSide(color: Colors.blue, width: 2),
|
||||
shape: StadiumBorder(),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlineButton(
|
||||
onPressed: gotoRecommend,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.whatshot,
|
||||
color: Colors.red,
|
||||
),
|
||||
Text(
|
||||
'月排行榜',
|
||||
style: TextStyle(color: Colors.red),
|
||||
)
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: OutlineButton(
|
||||
onPressed: gotoRecommend,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.whatshot,
|
||||
color: Colors.red,
|
||||
),
|
||||
Text(
|
||||
'月排行榜',
|
||||
style: TextStyle(color: Colors.red),
|
||||
)
|
||||
],
|
||||
),
|
||||
borderSide: BorderSide(color: Colors.red, width: 2),
|
||||
shape: StadiumBorder(),
|
||||
),
|
||||
borderSide: BorderSide(color: Colors.red, width: 2),
|
||||
shape: StadiumBorder(),
|
||||
),
|
||||
// SizedBox(width: 10),
|
||||
// Expanded(
|
||||
// flex: 3,
|
||||
// child: OutlineButton(
|
||||
// onPressed: gotoPatreon,
|
||||
// child: Text.rich(
|
||||
// TextSpan(children: [TextSpan(text: '赞助')]),
|
||||
// style: TextStyle(color: Colors.red),
|
||||
// ),
|
||||
// borderSide: BorderSide(color: Colors.red, width: 2),
|
||||
// shape: StadiumBorder(),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: Quick(
|
||||
key: _quickState,
|
||||
width: width,
|
||||
draggableModeChanged: _draggableModeChanged,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 10),
|
||||
child: Text(
|
||||
'在 level-plus.net 论坛首发',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
if (await canLaunch('tg://resolve?domain=weiman_app'))
|
||||
launch('tg://resolve?domain=weiman_app');
|
||||
else
|
||||
launch('https://t.me/weiman_app');
|
||||
},
|
||||
child: Text(
|
||||
'关注 Telegram 广播频道',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.blue[200],
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: Quick(
|
||||
key: _quickState,
|
||||
width: width,
|
||||
draggableModeChanged: _draggableModeChanged,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 10),
|
||||
child: Text(
|
||||
'在 level-plus.net 论坛首发',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey[500]),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
if (await canLaunch('tg://resolve?domain=weiman_app'))
|
||||
launch('tg://resolve?domain=weiman_app');
|
||||
else
|
||||
launch('https://t.me/weiman_app');
|
||||
},
|
||||
child: Text(
|
||||
'Telegram广播频道',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.blue[200],
|
||||
decoration: TextDecoration.underline,
|
||||
Visibility(
|
||||
visible: isDevMode,
|
||||
child: FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => ActivityTest()));
|
||||
},
|
||||
child: Text('测试界面'),
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: isDevMode,
|
||||
child: FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => ActivityTest()));
|
||||
},
|
||||
child: Text('测试界面'),
|
||||
Visibility(
|
||||
visible: isDevMode,
|
||||
child: FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => ActivityCheckData()));
|
||||
},
|
||||
child: Text('操作 收藏列表数据'),
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: isDevMode,
|
||||
child: FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.push(context,
|
||||
MaterialPageRoute(builder: (_) => ActivityCheckData()));
|
||||
},
|
||||
child: Text('操作 收藏列表数据'),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
86
lib/activities/rank.dart
Normal file
86
lib/activities/rank.dart
Normal file
@ -0,0 +1,86 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class ActivityRank extends StatefulWidget {
|
||||
@override
|
||||
_ActivityRank createState() => _ActivityRank();
|
||||
}
|
||||
|
||||
class _ActivityRank extends State<ActivityRank> {
|
||||
final List<Book> books = [];
|
||||
int page = 1;
|
||||
LoadMoreD _d;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_d = LoadMoreD(reload);
|
||||
}
|
||||
|
||||
void reload() {
|
||||
onLoadMore();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('月排行榜')),
|
||||
body: Container(
|
||||
child: LoadMore(
|
||||
isFinish: page >= 10,
|
||||
onLoadMore: onLoadMore,
|
||||
delegate: _d,
|
||||
child: ListView.builder(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return WidgetBook(
|
||||
books[index],
|
||||
subtitle: books[index].author,
|
||||
);
|
||||
},
|
||||
itemCount: books.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> onLoadMore() async {
|
||||
bool isSuccess = false;
|
||||
try {
|
||||
books.addAll(await HttpHHMH39.instance.getMonthList(page: page));
|
||||
isSuccess = true;
|
||||
print('load $page');
|
||||
page++;
|
||||
setState(() {});
|
||||
} catch (e) {
|
||||
print('$e $page');
|
||||
}
|
||||
return isSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
class LoadMoreD extends LoadMoreDelegate {
|
||||
final void Function() reload;
|
||||
|
||||
LoadMoreD(this.reload);
|
||||
|
||||
@override
|
||||
Widget buildChild(LoadMoreStatus status,
|
||||
{builder = DefaultLoadMoreTextBuilder.chinese}) {
|
||||
Widget widget;
|
||||
switch (status) {
|
||||
case LoadMoreStatus.idle:
|
||||
widget = SizedBox();
|
||||
break;
|
||||
case LoadMoreStatus.loading:
|
||||
widget = SafeArea(child: Center(child: Text('读取中')));
|
||||
break;
|
||||
case LoadMoreStatus.fail:
|
||||
widget = SafeArea(child: Center(child: Text('读取失败,点击再次尝试')));
|
||||
break;
|
||||
case LoadMoreStatus.nomore:
|
||||
widget = SafeArea(child: Center(child: Text('就这些了')));
|
||||
break;
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ class SearchState extends State<Search> {
|
||||
});
|
||||
_books.clear();
|
||||
try {
|
||||
final List<Book> books = await UserAgentClient.instance
|
||||
final List<Book> books = await HttpHHMH39.instance
|
||||
.searchBook(_controller.text)
|
||||
.timeout(Duration(seconds: 5));
|
||||
_books.addAll(books);
|
||||
@ -47,52 +47,56 @@ class SearchState extends State<Search> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey: (RawKeyEvent event) {
|
||||
print('is enter: ${LogicalKeyboardKey.enter == event.logicalKey}');
|
||||
if (_controller.text.isEmpty) return;
|
||||
if (event.runtimeType == RawKeyUpEvent &&
|
||||
LogicalKeyboardKey.enter == event.logicalKey) {
|
||||
print('回车键搜索');
|
||||
submit();
|
||||
}
|
||||
},
|
||||
child: FocusWidget.builder(
|
||||
context,
|
||||
(_, focusNode) => TextField(
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration(
|
||||
hintText: '搜索书名',
|
||||
prefixIcon: IconButton(
|
||||
onPressed: () {
|
||||
_refresh.currentState.show(
|
||||
notificationDragOffset:
|
||||
SliverPullToRefreshHeader.height);
|
||||
},
|
||||
icon: Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
controller: _controller,
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.search,
|
||||
onSubmitted: (String name) {
|
||||
focusNode.unfocus();
|
||||
print('onSubmitted');
|
||||
submit();
|
||||
},
|
||||
keyboardType: TextInputType.text,
|
||||
onEditingComplete: () {
|
||||
focusNode.unfocus();
|
||||
print('onEditingComplete');
|
||||
submit();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: PullToRefreshNotification(
|
||||
key: _refresh,
|
||||
onRefresh: startSearch,
|
||||
child: CustomScrollView(slivers: [
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
title: RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKey: (RawKeyEvent event) {
|
||||
print(
|
||||
'is enter: ${LogicalKeyboardKey.enter == event.logicalKey}');
|
||||
if (_controller.text.isEmpty) return;
|
||||
if (event.runtimeType == RawKeyUpEvent &&
|
||||
LogicalKeyboardKey.enter == event.logicalKey) {
|
||||
print('回车键搜索');
|
||||
submit();
|
||||
}
|
||||
},
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
hintText: '搜索书名',
|
||||
prefixIcon: IconButton(
|
||||
onPressed: () {
|
||||
_refresh.currentState.show(
|
||||
notificationDragOffset:
|
||||
SliverPullToRefreshHeader.height);
|
||||
},
|
||||
icon: Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
controller: _controller,
|
||||
autofocus: true,
|
||||
textInputAction: TextInputAction.search,
|
||||
onSubmitted: (String name) {
|
||||
print('onSubmitted');
|
||||
submit();
|
||||
},
|
||||
keyboardType: TextInputType.text,
|
||||
onEditingComplete: () {
|
||||
print('onEditingComplete');
|
||||
submit();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
PullToRefreshContainer((info) => SliverPullToRefreshHeader(
|
||||
info: info,
|
||||
onTap: submit,
|
||||
|
160
lib/activities/setting.dart
Normal file
160
lib/activities/setting.dart
Normal file
@ -0,0 +1,160 @@
|
||||
part of '../main.dart';
|
||||
|
||||
enum AutoCheckLevel {
|
||||
none,
|
||||
onlyInWeek,
|
||||
all,
|
||||
}
|
||||
|
||||
class SettingData extends ChangeNotifier {
|
||||
static final String key = 'setting_data';
|
||||
AutoCheckLevel _autoCheck;
|
||||
|
||||
SettingData() {
|
||||
final Map<String, dynamic> data =
|
||||
jsonDecode(Data.instance.getString(key) ?? '{}');
|
||||
_autoCheck = data['autoCheck'] == null
|
||||
? AutoCheckLevel.onlyInWeek
|
||||
: AutoCheckLevel.values[data['autoCheck']];
|
||||
print('SettingData');
|
||||
print(toJson());
|
||||
}
|
||||
|
||||
get autoCheck => _autoCheck;
|
||||
|
||||
set autoCheck(AutoCheckLevel val) {
|
||||
_autoCheck = val;
|
||||
notifyListeners();
|
||||
save();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {'autoCheck': _autoCheck.index};
|
||||
}
|
||||
|
||||
void save() {
|
||||
Data.instance.setString(key, jsonEncode(toJson()));
|
||||
}
|
||||
}
|
||||
|
||||
class ActivitySetting extends StatefulWidget {
|
||||
@override
|
||||
_ActivitySetting createState() => _ActivitySetting();
|
||||
}
|
||||
|
||||
class _ActivitySetting extends State<ActivitySetting> {
|
||||
static final Map<String, AutoCheckLevel> levels = {
|
||||
'不检查': AutoCheckLevel.none,
|
||||
'7天内看过': AutoCheckLevel.onlyInWeek,
|
||||
'全部': AutoCheckLevel.all
|
||||
};
|
||||
int imagesCount, sizeCount;
|
||||
bool isClearing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
imageCaches();
|
||||
}
|
||||
|
||||
Future<void> imageCaches() async {
|
||||
final dir = await getTemporaryDirectory();
|
||||
final cacheDir = Directory(path.join(dir.path, CacheImageFolderName));
|
||||
if (cacheDir.existsSync() == false) cacheDir.createSync();
|
||||
final files = cacheDir.listSync();
|
||||
imagesCount = files.length;
|
||||
sizeCount = 0;
|
||||
files.forEach((file) => sizeCount += file.statSync().size);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('设置')),
|
||||
body: Consumer<SettingData>(
|
||||
builder: (_, data, __) => ListView(
|
||||
children: ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: [
|
||||
ListTile(
|
||||
title: Text('清除所有图片缓存'),
|
||||
subtitle: isClearing
|
||||
? Text('清理中')
|
||||
: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: '图片数量:'),
|
||||
TextSpan(
|
||||
text: imagesCount == null
|
||||
? '读取中'
|
||||
: '$imagesCount 张'),
|
||||
TextSpan(text: '\n'),
|
||||
TextSpan(text: '存储容量:'),
|
||||
TextSpan(
|
||||
text: sizeCount == null
|
||||
? '读取中'
|
||||
: '${filesize(sizeCount)}'),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
final makeSure = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
title: Text('确认清除所有图片缓存?'),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: Text('取消'),
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
),
|
||||
RaisedButton(
|
||||
child: Text('确认'),
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (makeSure == false) return;
|
||||
showToast('正在清理图片缓存');
|
||||
isClearing = true;
|
||||
setState(() {});
|
||||
await clearDiskCachedImages();
|
||||
isClearing = false;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
await imageCaches();
|
||||
}
|
||||
showToast('成功清理图片缓存');
|
||||
},
|
||||
),
|
||||
autoCheck(data),
|
||||
],
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget autoCheck(SettingData data) {
|
||||
return ListTile(
|
||||
title: Text('自动检查收藏漫画的更新'),
|
||||
subtitle: Text('每次启动App后检查一次更新\n有很多漫画收藏的建议只检查7天内看过的漫画'),
|
||||
trailing: DropdownButton<AutoCheckLevel>(
|
||||
value: data.autoCheck,
|
||||
items: levels.keys
|
||||
.map(
|
||||
(key) => DropdownMenuItem(
|
||||
child: Text(key),
|
||||
value: levels[key],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (level) {
|
||||
data.autoCheck = level;
|
||||
// setState(() {});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class Book {
|
||||
static String cachePath;
|
||||
final String aid; // 书本ID
|
||||
final String name; // 书本名称
|
||||
final String avatar; // 书本封面
|
||||
@ -8,6 +9,7 @@ class Book {
|
||||
final String description; // 描述
|
||||
final List<Chapter> chapters;
|
||||
final int chapterCount;
|
||||
final bool fromCache;
|
||||
|
||||
History history;
|
||||
|
||||
@ -19,8 +21,16 @@ class Book {
|
||||
this.description,
|
||||
this.chapters: const [],
|
||||
this.chapterCount: 0,
|
||||
this.fromCache: false,
|
||||
this.history,
|
||||
});
|
||||
|
||||
static Future<void> initCachePath() async {
|
||||
final Directory dir = await getTemporaryDirectory();
|
||||
if (!dir.existsSync()) dir.createSync();
|
||||
cachePath = path.join(dir.path, 'books');
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode(toJson());
|
||||
@ -31,13 +41,6 @@ class Book {
|
||||
return books.containsKey(aid);
|
||||
}
|
||||
|
||||
favorite() {
|
||||
if (isFavorite())
|
||||
Data.removeFavorite(this);
|
||||
else
|
||||
Data.addFavorite(this);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = {
|
||||
'aid': aid,
|
||||
@ -63,6 +66,48 @@ class Book {
|
||||
book.history = History.fromJson(json['history']);
|
||||
return book;
|
||||
}
|
||||
|
||||
Future<void> saveBookCache() async {
|
||||
final file = File(path.join(cachePath, '$aid.json'));
|
||||
if (!file.existsSync()) file.createSync(recursive: true);
|
||||
await file.writeAsString(
|
||||
jsonEncode(
|
||||
{
|
||||
'aid': aid,
|
||||
'name': name,
|
||||
'author': author,
|
||||
'avatar': avatar,
|
||||
'description': description,
|
||||
'chapters':
|
||||
chapters.map<String>((chapter) => chapter.toString()).toList(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Book> loadBookCache(String aid) async {
|
||||
final file = File(path.join(cachePath, '$aid.json'));
|
||||
print('loadBookCache ${file.path}');
|
||||
if (file.existsSync()) {
|
||||
final json = jsonDecode(await file.readAsString());
|
||||
final List<dynamic> chapters = json['chapters'] ?? [];
|
||||
print('chapters ${json['chapters'][0]}');
|
||||
return Book(
|
||||
aid: json['aid'],
|
||||
name: json['name'],
|
||||
avatar: json['avatar'],
|
||||
description: json['description'],
|
||||
author: json['author'],
|
||||
fromCache: true,
|
||||
chapters: chapters.map((str) => Chapter.fromJsonString(str)).toList(),
|
||||
chapterCount: chapters.length,
|
||||
history: json['history'] == null
|
||||
? null
|
||||
: History.fromJson(jsonDecode(json['history'])),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Chapter {
|
||||
@ -70,11 +115,48 @@ class Chapter {
|
||||
final String cname; // 章节名称
|
||||
final String avatar; // 章节封面
|
||||
|
||||
Chapter({@required this.cid, @required this.cname, @required this.avatar});
|
||||
Chapter({
|
||||
@required this.cid,
|
||||
@required this.cname,
|
||||
@required this.avatar,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return jsonEncode({cid: cid, cname: cname, avatar: avatar});
|
||||
final Map<String, String> data = {
|
||||
'cid': cid,
|
||||
'cname': cname,
|
||||
'avatar': avatar,
|
||||
};
|
||||
return jsonEncode(data);
|
||||
}
|
||||
|
||||
static Chapter fromJsonString(String str) {
|
||||
return fromJson(jsonDecode(str));
|
||||
}
|
||||
|
||||
static List<String> fromCache(Book book, Chapter chapter) {
|
||||
final file =
|
||||
File(path.join(Book.cachePath, '${book.aid}_${chapter.cid}.json'));
|
||||
if (file.existsSync()) return List<String>.from(jsonDecode(file.readAsStringSync()));
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<void> saveCache(
|
||||
Book book, Chapter chapter, List<String> images) async {
|
||||
print('chapter save cache ${chapter.cid}');
|
||||
final file =
|
||||
File(path.join(Book.cachePath, '${book.aid}_${chapter.cid}.json'));
|
||||
if (file.existsSync() == false) file.createSync();
|
||||
return file.writeAsString(jsonEncode(images));
|
||||
}
|
||||
|
||||
static fromJson(data) {
|
||||
return Chapter(
|
||||
cid: data['cid'],
|
||||
cname: data['cname'],
|
||||
avatar: data['avatar'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,48 +0,0 @@
|
||||
part of '../main.dart';
|
||||
|
||||
const domain = '';
|
||||
|
||||
class UserAgentClient extends http.BaseClient {
|
||||
http.Client _inner = http.Client();
|
||||
String lastKey;
|
||||
int lastKeyTime = 0;
|
||||
|
||||
static UserAgentClient instance;
|
||||
|
||||
// UserAgentClient(this.userAgent);
|
||||
|
||||
UserAgentClient(String userAgent, ByteData data) {
|
||||
}
|
||||
|
||||
Future<String> getKey() async {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) {
|
||||
}
|
||||
|
||||
Future<List<String>> getImages(
|
||||
{@required String aid, @required String cid}) async {
|
||||
}
|
||||
|
||||
Future<Book> getBook({String aid}) async {
|
||||
}
|
||||
|
||||
static String _decrypt({String key, String content}) {
|
||||
}
|
||||
|
||||
Future<List<Book>> searchBook(String name) async {
|
||||
}
|
||||
|
||||
static void init() async {
|
||||
}
|
||||
|
||||
Future<http.Response> _get(url, {Map<String, String> headers}) async {
|
||||
}
|
||||
|
||||
Future<List<Book>> getMonthList() async {
|
||||
}
|
||||
|
||||
Future<List<Book>> getIndexRandomBooks() async {
|
||||
}
|
||||
}
|
@ -7,10 +7,15 @@ class NetworkImageSSL
|
||||
/// Creates an object that fetches the image at the given URL.
|
||||
///
|
||||
/// The arguments [url] and [scale] must not be null.
|
||||
const NetworkImageSSL(this.url, {this.scale = 1.0, this.headers})
|
||||
: assert(url != null),
|
||||
const NetworkImageSSL(
|
||||
this.url, {
|
||||
this.scale = 1.0,
|
||||
this.headers,
|
||||
this.timeout = 8,
|
||||
}) : assert(url != null),
|
||||
assert(scale != null);
|
||||
|
||||
final int timeout;
|
||||
@override
|
||||
final String url;
|
||||
|
||||
@ -72,8 +77,9 @@ class NetworkImageSSL
|
||||
assert(key == this);
|
||||
|
||||
final Uri resolved = Uri.base.resolve(key.url);
|
||||
final HttpClientRequest request =
|
||||
await _httpClient.getUrl(resolved).timeout(Duration(seconds: 5));
|
||||
final HttpClientRequest request = await _httpClient
|
||||
.getUrl(resolved)
|
||||
.timeout(Duration(seconds: timeout));
|
||||
headers?.forEach((String name, String value) {
|
||||
request.headers.add(name, value);
|
||||
});
|
||||
|
30
lib/crawler/http.dart
Normal file
30
lib/crawler/http.dart
Normal file
@ -0,0 +1,30 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class MyHttpClient {
|
||||
static Future init() async {
|
||||
final ca = await rootBundle.load('assets/ca.crt');
|
||||
final SecurityContext context = SecurityContext.defaultContext;
|
||||
context.setTrustedCertificatesBytes(ca.buffer.asUint8List());
|
||||
|
||||
final version = Random().nextInt(10) + 72;
|
||||
final userAgent =
|
||||
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/$version.0.4056.3 Mobile Safari/537.36';
|
||||
|
||||
final ioClient = new HttpClient()
|
||||
..badCertificateCallback = (_, __, ___) => true;
|
||||
ioClient.userAgent = userAgent;
|
||||
final client = IOClient(ioClient);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class UserAgentClient extends http.BaseClient {
|
||||
final http.Client inner;
|
||||
|
||||
UserAgentClient(this.inner);
|
||||
|
||||
Future<List<Book>> searchBook(String name);
|
||||
|
||||
Future<Book> getBook(String aid);
|
||||
|
||||
Future<List<String>> getChapterImages(Book book, Chapter chapter);
|
||||
}
|
@ -3,36 +3,39 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:draggable_container/draggable_container.dart';
|
||||
import 'package:dynamic_theme/dynamic_theme.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:filesize/filesize.dart';
|
||||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:firebase_analytics/observer.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' hide NestedScrollView;
|
||||
import 'package:flutter/painting.dart' as image_provider;
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
|
||||
import 'package:focus_widget/focus_widget.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:html/parser.dart' as html;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:http_client_helper/http_client_helper.dart';
|
||||
import 'package:loadmore/loadmore.dart';
|
||||
import 'package:oktoast/oktoast.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart'
|
||||
hide CircularProgressIndicator;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sticky_headers/sticky_headers/widget.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart' as image_provider;
|
||||
|
||||
part './activities/book.dart';
|
||||
|
||||
@ -46,16 +49,22 @@ part './activities/rank.dart';
|
||||
|
||||
part './activities/search.dart';
|
||||
|
||||
part './activities/setting.dart';
|
||||
|
||||
part './activities/test.dart';
|
||||
|
||||
part './classes/book.dart';
|
||||
|
||||
part './classes/data.dart';
|
||||
|
||||
part './classes/http.dart';
|
||||
|
||||
part './classes/networkImageSSL.dart';
|
||||
|
||||
part './crawler/hhmh39.dart';
|
||||
|
||||
part './crawler/http.dart';
|
||||
|
||||
part './crawler/httpHanManJia.dart';
|
||||
|
||||
part './widgets/book.dart';
|
||||
|
||||
part './widgets/favorites.dart';
|
||||
@ -88,13 +97,22 @@ void main() async {
|
||||
|
||||
await Future.wait([
|
||||
Data.init(),
|
||||
Book.initCachePath(),
|
||||
MyHttpClient.init(),
|
||||
SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
|
||||
]);
|
||||
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
UserAgentClient.init();
|
||||
runApp(Main(packageInfo: packageInfo));
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<SettingData>(create: (_) => SettingData()),
|
||||
ChangeNotifierProvider<FavoriteData>(create: (_) => FavoriteData()),
|
||||
],
|
||||
child: Main(packageInfo: packageInfo),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class Main extends StatefulWidget {
|
||||
@ -109,6 +127,12 @@ class Main extends StatefulWidget {
|
||||
class _Main extends State<Main> with WidgetsBindingObserver {
|
||||
static BoxDecoration _border;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
HttpClientHelper().set(HttpHHMH39.instance.inner);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_border == null)
|
||||
@ -123,7 +147,7 @@ class _Main extends State<Main> with WidgetsBindingObserver {
|
||||
themedWidgetBuilder: (context, theme) {
|
||||
return OKToast(
|
||||
child: MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
title: '微漫 v${widget.packageInfo.version}',
|
||||
theme: theme,
|
||||
home: ActivityHome(widget.packageInfo),
|
||||
),
|
||||
|
@ -28,12 +28,13 @@ class WidgetBook extends StatelessWidget {
|
||||
),
|
||||
dense: true,
|
||||
leading: Hero(
|
||||
tag: 'bookAvatar${book.aid}',
|
||||
child: Image(image:NetworkImageSSL(
|
||||
book.avatar),
|
||||
height: 200,
|
||||
fit: BoxFit.scaleDown,
|
||||
)),
|
||||
tag: 'bookAvatar${book.aid}',
|
||||
child: Image(
|
||||
image: ExtendedNetworkImageProvider(book.avatar, cache: true),
|
||||
height: 200,
|
||||
fit: BoxFit.scaleDown,
|
||||
),
|
||||
),
|
||||
trailing: Icon(
|
||||
isLiked ? Icons.favorite : Icons.favorite_border,
|
||||
color: isLiked ? Colors.red : Colors.grey,
|
||||
@ -57,7 +58,7 @@ class WidgetChapter extends StatelessWidget {
|
||||
Key key,
|
||||
this.chapter,
|
||||
this.onTap,
|
||||
this.read,
|
||||
this.read = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -83,11 +84,16 @@ class WidgetChapter extends StatelessWidget {
|
||||
softWrap: true,
|
||||
maxLines: 2,
|
||||
),
|
||||
leading: Image(image:NetworkImageSSL(
|
||||
chapter.avatar),
|
||||
fit: BoxFit.fitWidth,
|
||||
width: 100,
|
||||
),
|
||||
leading: chapter.avatar == null
|
||||
? null
|
||||
: Image(
|
||||
image: ExtendedNetworkImageProvider(
|
||||
chapter.avatar,
|
||||
cache: true,
|
||||
),
|
||||
fit: BoxFit.fitWidth,
|
||||
width: 100,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -106,8 +112,8 @@ class WidgetHistory extends StatelessWidget {
|
||||
if (onTap != null) onTap(book);
|
||||
},
|
||||
title: Text(book.name),
|
||||
leading: Image(image:NetworkImageSSL(
|
||||
book.avatar),
|
||||
leading: Image(
|
||||
image: ExtendedNetworkImageProvider(book.avatar, cache: true),
|
||||
fit: BoxFit.fitHeight,
|
||||
),
|
||||
subtitle: Text(book.history.cname),
|
||||
@ -138,8 +144,8 @@ class _WidgetBookCheckNew extends State<WidgetBookCheckNew> {
|
||||
void load() async {
|
||||
loading = true;
|
||||
try {
|
||||
final book = await UserAgentClient.instance
|
||||
.getBook(aid: widget.book.aid)
|
||||
final book = await HttpHHMH39.instance
|
||||
.getBook(widget.book.aid)
|
||||
.timeout(Duration(seconds: 2));
|
||||
news = book.chapterCount - widget.book.chapterCount;
|
||||
hasError = false;
|
||||
@ -173,7 +179,9 @@ class _WidgetBookCheckNew extends State<WidgetBookCheckNew> {
|
||||
openBook(context, widget.book, 'checkBook${widget.book.aid}'),
|
||||
leading: Hero(
|
||||
tag: 'checkBook${widget.book.aid}',
|
||||
child: Image(image:NetworkImageSSL(widget.book.avatar)),
|
||||
child: Image(
|
||||
image:
|
||||
ExtendedNetworkImageProvider(widget.book.avatar, cache: true)),
|
||||
),
|
||||
dense: true,
|
||||
isThreeLine: true,
|
||||
|
@ -1,15 +1,88 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class FavoriteData extends ChangeNotifier {
|
||||
/// -2 在队列中等待检查,-1读取错误,0 没有更新,> 0 更新的章节数量
|
||||
final Map<String, int> hasNews = {}; // 漫画的状态
|
||||
final Map<String, Book> all = {}, // 所有收藏
|
||||
inWeek = {}, // 7天内看过的收藏
|
||||
other = {}; // 其他收藏
|
||||
bool _loading;
|
||||
|
||||
FavoriteData() {
|
||||
loadBooksList();
|
||||
}
|
||||
|
||||
Future<void> loadBooksList() async {
|
||||
all
|
||||
..clear()
|
||||
..addAll(Data.getFavorites());
|
||||
calcBookHistory();
|
||||
}
|
||||
|
||||
void add(Book book) {
|
||||
Data.addFavorite(book);
|
||||
all[book.aid] = book;
|
||||
calcBookHistory();
|
||||
}
|
||||
|
||||
void remove(Book book) {
|
||||
Data.removeFavorite(book);
|
||||
all.remove(book.aid);
|
||||
calcBookHistory();
|
||||
}
|
||||
|
||||
void calcBookHistory() {
|
||||
inWeek.clear();
|
||||
other.clear();
|
||||
if (all.isNotEmpty) {
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
all.forEach((aid, book) {
|
||||
if (book.history != null && (now - book.history.time) < weekTime) {
|
||||
inWeek[aid] = book;
|
||||
} else {
|
||||
other[aid] = book;
|
||||
}
|
||||
});
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> checkNews(AutoCheckLevel level) async {
|
||||
if (level == AutoCheckLevel.none) return;
|
||||
final _books = level == AutoCheckLevel.onlyInWeek ? inWeek : all;
|
||||
final keys = _books.keys;
|
||||
hasNews
|
||||
..clear()
|
||||
..addAll(_books.map((aid, book) => MapEntry(aid, -2)));
|
||||
notifyListeners();
|
||||
Book currentBook, newBook;
|
||||
for (var i = 0; i < _books.length; i++) {
|
||||
currentBook = _books[keys.elementAt(i)];
|
||||
try {
|
||||
newBook = await HttpHHMH39.instance
|
||||
.getBook(currentBook.aid)
|
||||
.timeout(Duration(seconds: 8));
|
||||
int different = newBook.chapterCount - currentBook.chapterCount;
|
||||
hasNews[currentBook.aid] = different;
|
||||
if (different > 0) {
|
||||
newBook.history = Data.getHistories()[newBook.aid]?.history;
|
||||
Data.addFavorite(newBook);
|
||||
newBook.saveBookCache();
|
||||
}
|
||||
} catch (e) {
|
||||
hasNews[currentBook.aid] = -1;
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
class FavoriteList extends StatefulWidget {
|
||||
@override
|
||||
_FavoriteList createState() => _FavoriteList();
|
||||
}
|
||||
|
||||
class _FavoriteList extends State<FavoriteList> {
|
||||
static final Map<String, int> hasNews = {};
|
||||
static final List<Book> all = [], // 所有收藏
|
||||
inWeek = [], // 7天看过的收藏
|
||||
other = []; // 其他收藏
|
||||
static bool showTip = false;
|
||||
|
||||
static final loadFailTextSpan = TextSpan(
|
||||
@ -20,140 +93,100 @@ class _FavoriteList extends State<FavoriteList> {
|
||||
TextSpan(text: '请下拉列表检查更新', style: TextStyle(color: Colors.grey)),
|
||||
noUpdate = TextSpan(text: '没有更新', style: TextStyle(color: Colors.grey));
|
||||
|
||||
static void getBooks() {
|
||||
all.clear();
|
||||
inWeek.clear();
|
||||
other.clear();
|
||||
all.addAll(Data.getFavorites().values);
|
||||
if (all.isNotEmpty) {
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
all.forEach((book) {
|
||||
if (book.history != null && (now - book.history.time) < weekTime) {
|
||||
inWeek.add(book);
|
||||
} else {
|
||||
other.add(book);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
getBooks();
|
||||
if (all.isNotEmpty) {
|
||||
if (showTip == false) {
|
||||
showTip = true;
|
||||
showToast(
|
||||
'下拉列表可以检查漫画更新',
|
||||
backgroundColor: Colors.black.withOpacity(0.5),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _openBook(book) {
|
||||
openBook(context, book, 'fb ${book.aid}');
|
||||
}
|
||||
|
||||
static Future<void> checkNews() async {
|
||||
hasNews.clear();
|
||||
Book currentBook, newBook;
|
||||
int different;
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
currentBook = all[i];
|
||||
try {
|
||||
newBook = await UserAgentClient.instance
|
||||
.getBook(aid: currentBook.aid)
|
||||
.timeout(Duration(seconds: 2));
|
||||
different = newBook.chapterCount - currentBook.chapterCount;
|
||||
hasNews[currentBook.aid] = different;
|
||||
} catch (e) {
|
||||
hasNews[currentBook.aid] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Widget bookBuilder(Book book) {
|
||||
TextSpan state;
|
||||
if (hasNews.isEmpty) {
|
||||
state = unCheck;
|
||||
} else {
|
||||
if (hasNews.containsKey(book.aid)) {
|
||||
if (hasNews[book.aid] > 0) {
|
||||
state = TextSpan(
|
||||
text: '有 ${hasNews[book.aid]} 章更新',
|
||||
style: TextStyle(color: Colors.green));
|
||||
} else if (hasNews[book.aid] == -1) {
|
||||
state = loadFailTextSpan;
|
||||
} else if (hasNews[book.aid] == 0) {
|
||||
state = noUpdate;
|
||||
}
|
||||
} else {
|
||||
state = waitToCheck;
|
||||
}
|
||||
Widget bookBuilder(Book book, int state) {
|
||||
TextSpan _state = unCheck;
|
||||
if (state == null) {
|
||||
_state = unCheck;
|
||||
} else if (state > 0) {
|
||||
_state =
|
||||
TextSpan(text: '有 $state 章更新', style: TextStyle(color: Colors.green));
|
||||
} else if (state == 0) {
|
||||
_state = noUpdate;
|
||||
} else if (state == -1) {
|
||||
_state = loadFailTextSpan;
|
||||
} else if (state == -2) {
|
||||
_state = waitToCheck;
|
||||
}
|
||||
return FBookItem(
|
||||
book: book,
|
||||
subtitle: state,
|
||||
subtitle: _state,
|
||||
onTap: _openBook,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Book> inWeekUpdated = [],
|
||||
inWeekUnUpdated = [],
|
||||
otherUpdated = [],
|
||||
otherUnUpdated = [];
|
||||
inWeek.forEach((book) {
|
||||
if (hasNews.containsKey(book.aid) && hasNews[book.aid] > 0)
|
||||
inWeekUpdated.add(book);
|
||||
else
|
||||
inWeekUnUpdated.add(book);
|
||||
return Consumer2<SettingData, FavoriteData>(
|
||||
builder: (_, setting, favorite, __) {
|
||||
if (favorite.all.isEmpty) return Center(child: Text('没有收藏'));
|
||||
List<Book> inWeekUpdated = [],
|
||||
inWeekUnUpdated = [],
|
||||
otherUpdated = [],
|
||||
otherUnUpdated = [];
|
||||
favorite.inWeek.forEach((aid, book) {
|
||||
if (favorite.hasNews.containsKey(book.aid) &&
|
||||
favorite.hasNews[book.aid] > 0)
|
||||
inWeekUpdated.add(book);
|
||||
else
|
||||
inWeekUnUpdated.add(book);
|
||||
});
|
||||
favorite.other.forEach((aid, book) {
|
||||
if (favorite.hasNews.containsKey(book.aid) &&
|
||||
favorite.hasNews[book.aid] > 0)
|
||||
otherUpdated.add(book);
|
||||
else
|
||||
otherUnUpdated.add(book);
|
||||
});
|
||||
return Drawer(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
favorite.checkNews(AutoCheckLevel.all);
|
||||
},
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverExpandableGroup(
|
||||
title: Text('7天内看过并且有更新的藏书(${inWeekUpdated.length})'),
|
||||
expanded: true,
|
||||
count: inWeekUpdated.length,
|
||||
builder: (ctx, i) => bookBuilder(
|
||||
inWeekUpdated[i],
|
||||
favorite.hasNews[inWeekUpdated[i].aid],
|
||||
),
|
||||
),
|
||||
SliverExpandableGroup(
|
||||
title: Text('7天内看过的藏书(${inWeekUnUpdated.length})'),
|
||||
count: inWeekUnUpdated.length,
|
||||
builder: (ctx, i) => bookBuilder(
|
||||
inWeekUnUpdated[i],
|
||||
favorite.hasNews[inWeekUnUpdated[i].aid],
|
||||
),
|
||||
),
|
||||
SliverExpandableGroup(
|
||||
title: Text('有更新的藏书(${otherUpdated.length})'),
|
||||
count: otherUpdated.length,
|
||||
builder: (ctx, i) => bookBuilder(
|
||||
otherUpdated[i],
|
||||
favorite.hasNews[otherUpdated[i].aid],
|
||||
),
|
||||
),
|
||||
SliverExpandableGroup(
|
||||
title: Text('没有更新的藏书(${otherUnUpdated.length})'),
|
||||
count: otherUnUpdated.length,
|
||||
builder: (ctx, i) => bookBuilder(
|
||||
otherUnUpdated[i],
|
||||
favorite.hasNews[otherUnUpdated[i].aid],
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
});
|
||||
other.forEach((book) {
|
||||
if (hasNews.containsKey(book.aid) && hasNews[book.aid] > 0)
|
||||
otherUpdated.add(book);
|
||||
else
|
||||
otherUnUpdated.add(book);
|
||||
});
|
||||
return Drawer(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await checkNews();
|
||||
setState(() {});
|
||||
},
|
||||
child: all.isEmpty
|
||||
? Center(child: Text('没有收藏'))
|
||||
: SafeArea(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverExpandableGroup(
|
||||
title: Text('7天内看过并且有更新的藏书(${inWeekUpdated.length})'),
|
||||
expanded: true,
|
||||
count: inWeekUpdated.length,
|
||||
builder: (ctx, i) => bookBuilder(inWeekUpdated[i]),
|
||||
),
|
||||
SliverExpandableGroup(
|
||||
title: Text('7天内看过的藏书(${inWeekUnUpdated.length})'),
|
||||
count: inWeekUnUpdated.length,
|
||||
builder: (ctx, i) => bookBuilder(inWeekUnUpdated[i]),
|
||||
),
|
||||
SliverExpandableGroup(
|
||||
title: Text('有更新的藏书(${otherUpdated.length})'),
|
||||
count: otherUpdated.length,
|
||||
builder: (ctx, i) => bookBuilder(otherUpdated[i]),
|
||||
),
|
||||
SliverExpandableGroup(
|
||||
title: Text('没有更新的藏书(${otherUnUpdated.length})'),
|
||||
count: otherUnUpdated.length,
|
||||
builder: (ctx, i) => bookBuilder(otherUnUpdated[i]),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,8 +207,14 @@ class FBookItem extends StatelessWidget {
|
||||
return ListTile(
|
||||
onTap: () => onTap(book),
|
||||
leading: Hero(
|
||||
tag: 'fb ${book.aid}',
|
||||
child: Image(image: NetworkImageSSL(book.avatar))),
|
||||
tag: 'fb ${book.aid}',
|
||||
child: Image(
|
||||
image: ExtendedNetworkImageProvider(
|
||||
book.avatar,
|
||||
cache: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(book.name, style: Theme.of(context).textTheme.body1),
|
||||
subtitle: RichText(text: subtitle),
|
||||
);
|
||||
|
@ -55,7 +55,7 @@ class QuickBook extends DraggableItem {
|
||||
}
|
||||
|
||||
checkUpdate() {
|
||||
UserAgentClient.instance.getBook(aid: book.aid);
|
||||
HttpHHMH39.instance.getBook(book.aid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,11 +72,12 @@ class Quick extends StatefulWidget {
|
||||
}
|
||||
|
||||
class QuickState extends State<Quick> {
|
||||
final List<String> id = [];
|
||||
final int count = 8;
|
||||
final List<DraggableItem> _draggableItems = [];
|
||||
DraggableItem _addButton;
|
||||
GlobalKey<DraggableContainerState> _key = GlobalKey();
|
||||
final List<String> id = [];
|
||||
GlobalKey<DraggableContainerState> _key =
|
||||
GlobalKey<DraggableContainerState>();
|
||||
double width = 0, height = 0;
|
||||
|
||||
void exit() {
|
||||
@ -171,6 +172,25 @@ class QuickState extends State<Quick> {
|
||||
for (var i = count - _draggableItems.length; i > 0; i--) {
|
||||
_draggableItems.add(null);
|
||||
}
|
||||
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
print('添加监听');
|
||||
Provider.of<FavoriteData>(context, listen: false).addListener(refresh);
|
||||
});
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
final id = Data._quickIdList();
|
||||
// print('refresh $id');
|
||||
for (var i = 0; i < _draggableItems.length; i++) {
|
||||
final item = _draggableItems[i];
|
||||
if (item is QuickBook) {
|
||||
// print('is QuickBook,delete : ${id.contains(item.book.aid)}');
|
||||
if (!id.contains(item.book.aid)) {
|
||||
_key.currentState.insteadOfIndex(i, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
24
pubspec.yaml
24
pubspec.yaml
@ -1,5 +1,5 @@
|
||||
name: weiman
|
||||
description: 微漫
|
||||
description: 微漫App
|
||||
|
||||
# The following defines the version and build number for your application.
|
||||
# A version number is three numbers separated by dots, like 1.2.43
|
||||
@ -11,7 +11,7 @@ description: 微漫
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.0.4
|
||||
version: 1.0.8+2005
|
||||
|
||||
environment:
|
||||
sdk: ">=2.3.0 <3.0.0"
|
||||
@ -20,26 +20,34 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
async: any
|
||||
cupertino_icons: any
|
||||
async: any
|
||||
http: any
|
||||
encrypt: any
|
||||
html: any
|
||||
shared_preferences: any
|
||||
fluttertoast: any
|
||||
random_string: any
|
||||
filesize: any
|
||||
oktoast: any
|
||||
path_provider: any
|
||||
draggable_container: any
|
||||
sticky_headers: any
|
||||
flutter_sticky_header: any
|
||||
extended_nested_scroll_view: any
|
||||
dynamic_theme: any
|
||||
package_info: any
|
||||
url_launcher: any
|
||||
font_awesome_flutter: any
|
||||
loading_more_list: any
|
||||
loadmore: any
|
||||
pull_to_refresh_notification: any
|
||||
http_client_helper: any
|
||||
extended_image: any
|
||||
screenshot: any
|
||||
focus_widget: any
|
||||
provider: any
|
||||
|
||||
firebase_core: any
|
||||
firebase_analytics: any
|
||||
e2e: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -58,7 +66,9 @@ flutter:
|
||||
uses-material-design: true
|
||||
|
||||
assets:
|
||||
- images/logo.png
|
||||
- assets/logo.png
|
||||
- assets/ca.crt
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
|
Loading…
x
Reference in New Issue
Block a user