1
0
mirror of https://github.com/nrop19/weiman_app.git synced 2025-08-02 23:05:48 +08:00
This commit is contained in:
nrop19 2020-02-26 05:39:36 +08:00
parent 038f6d5eaf
commit f68617e44b
16 changed files with 997 additions and 507 deletions

View File

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

View File

@ -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) {

View File

@ -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;

View File

@ -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
View 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;
}
}

View File

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

View File

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

View File

@ -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 {
}
}

View File

@ -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
View 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);
}

View File

@ -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),
),

View File

@ -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,

View File

@ -1,15 +1,88 @@
part of '../main.dart';
class FavoriteData extends ChangeNotifier {
/// -2 -10 > 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),
);

View File

@ -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 QuickBookdelete : ${id.contains(item.book.aid)}');
if (!id.contains(item.book.aid)) {
_key.currentState.insteadOfIndex(i, null);
}
}
}
}
@override

View File

@ -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