diff --git a/lib/activities/book.dart b/lib/activities/book.dart index 45b15ea..74e600c 100644 --- a/lib/activities/book.dart +++ b/lib/activities/book.dart @@ -15,15 +15,12 @@ class BookState extends State { ScrollController _scrollController; bool _reverse = false; - bool isFavorite = false; - bool isLoading = true, isSuccess = false; Book book; - List 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 { } Future 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 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 { }); } - favoriteBook() { - widget.book.favorite(); - isFavorite = !isFavorite; + favoriteBook() async { + final fav = Provider.of(context, listen: false); + if (book.isFavorite()) { + final isFavoriteBook = Data._quickIdList().contains(book.aid); + if (isFavoriteBook) { + final sure = await showDialog( + 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 chapterWidgets() { - final book = this.book ?? widget.book; - List list = []; - chapters.forEach((chapter) { - final isRead = chapter.cid == book.history?.cid; - list.add(WidgetChapter( - chapter: chapter, - onTap: _openChapter, - read: isRead, - )); - }); + List _sort() { + final List 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 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 chapters = _sort(); + final history = []; + 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 { SliverAppBar( floating: true, pinned: true, - title: Text(widget.book.name), + title: Text(book.name), expandedHeight: 200, actions: [ IconButton( - onPressed: _sort, + onPressed: () { + setState(() { + _reverse = !_reverse; + setState(() {}); + }); + }, icon: Icon(_reverse ? FontAwesomeIcons.sortNumericDown : FontAwesomeIcons.sortNumericDownAlt)), @@ -175,8 +206,12 @@ class BookState extends State { 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 { 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, ), ), ], diff --git a/lib/activities/chapter.dart b/lib/activities/chapter.dart index 9af93a4..655958f 100644 --- a/lib/activities/chapter.dart +++ b/lib/activities/chapter.dart @@ -19,6 +19,7 @@ class ChapterState extends State { @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 { 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 { }, ), 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 { Future 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 { } 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 { ], ), ), - 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 { } }, ), -// 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> 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) { diff --git a/lib/activities/checkData.dart b/lib/activities/checkData.dart index 849d665..5a3c0b9 100644 --- a/lib/activities/checkData.dart +++ b/lib/activities/checkData.dart @@ -17,7 +17,7 @@ final titleTextStyle = TextStyle(fontSize: 14, color: Colors.blue), class _State extends State { CheckState firstState; - int firstLength; + int firstLength = 0; final TextSpan secondResults = TextSpan(); TextEditingController _outputController, _inputController; diff --git a/lib/activities/home.dart b/lib/activities/home.dart index 9998c57..51fc2cd 100644 --- a/lib/activities/home.dart +++ b/lib/activities/home.dart @@ -26,10 +26,11 @@ class HomeState extends State { /// 提前检查一次藏书的更新情况 SchedulerBinding.instance.addPostFrameCallback((_) async { autoSwitchTheme(); - _FavoriteList.getBooks(); - await _FavoriteList.checkNews(); - final updated = _FavoriteList.hasNews.values - .where((int updatedChapters) => updatedChapters > 0) + FavoriteData favData = Provider.of(context,listen: false); + await favData.loadBooksList(); + await favData.checkNews(Provider.of(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 { 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 { ), 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 { }, ), ), - body: Container( - alignment: Alignment.center, - padding: EdgeInsets.only(left: 40, right: 40), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - Container( - child: OutlineButton( - onPressed: gotoSearch, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - 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: [ + Container( + child: OutlineButton( + onPressed: gotoSearch, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + 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: [ - 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: [ + 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('操作 收藏列表数据'), - ), - ), - ], + ], + ), ), ), ); diff --git a/lib/activities/rank.dart b/lib/activities/rank.dart new file mode 100644 index 0000000..1b83fab --- /dev/null +++ b/lib/activities/rank.dart @@ -0,0 +1,86 @@ +part of '../main.dart'; + +class ActivityRank extends StatefulWidget { + @override + _ActivityRank createState() => _ActivityRank(); +} + +class _ActivityRank extends State { + final List 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 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; + } +} diff --git a/lib/activities/search.dart b/lib/activities/search.dart index 261841f..c805941 100644 --- a/lib/activities/search.dart +++ b/lib/activities/search.dart @@ -32,7 +32,7 @@ class SearchState extends State { }); _books.clear(); try { - final List books = await UserAgentClient.instance + final List books = await HttpHHMH39.instance .searchBook(_controller.text) .timeout(Duration(seconds: 5)); _books.addAll(books); @@ -47,52 +47,56 @@ class SearchState extends State { @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, diff --git a/lib/activities/setting.dart b/lib/activities/setting.dart new file mode 100644 index 0000000..3777d37 --- /dev/null +++ b/lib/activities/setting.dart @@ -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 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 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 { + static final Map levels = { + '不检查': AutoCheckLevel.none, + '7天内看过': AutoCheckLevel.onlyInWeek, + '全部': AutoCheckLevel.all + }; + int imagesCount, sizeCount; + bool isClearing = false; + + @override + void initState() { + super.initState(); + imageCaches(); + } + + Future 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( + 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( + 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( + value: data.autoCheck, + items: levels.keys + .map( + (key) => DropdownMenuItem( + child: Text(key), + value: levels[key], + ), + ) + .toList(), + onChanged: (level) { + data.autoCheck = level; +// setState(() {}); + }, + ), + ); + } +} diff --git a/lib/classes/book.dart b/lib/classes/book.dart index da74816..2eb0864 100644 --- a/lib/classes/book.dart +++ b/lib/classes/book.dart @@ -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 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 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 toJson() { final Map data = { 'aid': aid, @@ -63,6 +66,48 @@ class Book { book.history = History.fromJson(json['history']); return book; } + + Future 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((chapter) => chapter.toString()).toList(), + }, + ), + ); + } + + static Future 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 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 data = { + 'cid': cid, + 'cname': cname, + 'avatar': avatar, + }; + return jsonEncode(data); + } + + static Chapter fromJsonString(String str) { + return fromJson(jsonDecode(str)); + } + + static List fromCache(Book book, Chapter chapter) { + final file = + File(path.join(Book.cachePath, '${book.aid}_${chapter.cid}.json')); + if (file.existsSync()) return List.from(jsonDecode(file.readAsStringSync())); + return null; + } + + static Future saveCache( + Book book, Chapter chapter, List 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'], + ); } } diff --git a/lib/classes/http.dart b/lib/classes/http.dart deleted file mode 100644 index 37caa79..0000000 --- a/lib/classes/http.dart +++ /dev/null @@ -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 getKey() async { - } - - @override - Future send(http.BaseRequest request) { - } - - Future> getImages( - {@required String aid, @required String cid}) async { - } - - Future getBook({String aid}) async { - } - - static String _decrypt({String key, String content}) { - } - - Future> searchBook(String name) async { - } - - static void init() async { - } - - Future _get(url, {Map headers}) async { - } - - Future> getMonthList() async { - } - - Future> getIndexRandomBooks() async { - } -} diff --git a/lib/classes/networkImageSSL.dart b/lib/classes/networkImageSSL.dart index 65003e8..108249e 100644 --- a/lib/classes/networkImageSSL.dart +++ b/lib/classes/networkImageSSL.dart @@ -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); }); diff --git a/lib/crawler/http.dart b/lib/crawler/http.dart new file mode 100644 index 0000000..51b7c2f --- /dev/null +++ b/lib/crawler/http.dart @@ -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> searchBook(String name); + + Future getBook(String aid); + + Future> getChapterImages(Book book, Chapter chapter); +} diff --git a/lib/main.dart b/lib/main.dart index 601707f..1423bb7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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(create: (_) => SettingData()), + ChangeNotifierProvider(create: (_) => FavoriteData()), + ], + child: Main(packageInfo: packageInfo), + ), + ); } class Main extends StatefulWidget { @@ -109,6 +127,12 @@ class Main extends StatefulWidget { class _Main extends State
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
with WidgetsBindingObserver { themedWidgetBuilder: (context, theme) { return OKToast( child: MaterialApp( - title: 'Flutter Demo', + title: '微漫 v${widget.packageInfo.version}', theme: theme, home: ActivityHome(widget.packageInfo), ), diff --git a/lib/widgets/book.dart b/lib/widgets/book.dart index bd942b3..ea8da15 100644 --- a/lib/widgets/book.dart +++ b/lib/widgets/book.dart @@ -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 { 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 { 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, diff --git a/lib/widgets/favorites.dart b/lib/widgets/favorites.dart index 57b5169..d0e1d55 100644 --- a/lib/widgets/favorites.dart +++ b/lib/widgets/favorites.dart @@ -1,15 +1,88 @@ part of '../main.dart'; +class FavoriteData extends ChangeNotifier { + /// -2 在队列中等待检查,-1读取错误,0 没有更新,> 0 更新的章节数量 + final Map hasNews = {}; // 漫画的状态 + final Map all = {}, // 所有收藏 + inWeek = {}, // 7天内看过的收藏 + other = {}; // 其他收藏 + bool _loading; + + FavoriteData() { + loadBooksList(); + } + + Future 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 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 { - static final Map hasNews = {}; - static final List all = [], // 所有收藏 - inWeek = [], // 7天看过的收藏 - other = []; // 其他收藏 static bool showTip = false; static final loadFailTextSpan = TextSpan( @@ -20,140 +93,100 @@ class _FavoriteList extends State { 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 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 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( + builder: (_, setting, favorite, __) { + if (favorite.all.isEmpty) return Center(child: Text('没有收藏')); + List 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), ); diff --git a/lib/widgets/quick.dart b/lib/widgets/quick.dart index 63ba157..cbda087 100644 --- a/lib/widgets/quick.dart +++ b/lib/widgets/quick.dart @@ -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 { + final List id = []; final int count = 8; final List _draggableItems = []; DraggableItem _addButton; - GlobalKey _key = GlobalKey(); - final List id = []; + GlobalKey _key = + GlobalKey(); double width = 0, height = 0; void exit() { @@ -171,6 +172,25 @@ class QuickState extends State { for (var i = count - _draggableItems.length; i > 0; i--) { _draggableItems.add(null); } + + SchedulerBinding.instance.addPostFrameCallback((_) { + print('添加监听'); + Provider.of(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 diff --git a/pubspec.yaml b/pubspec.yaml index 99dc1cc..32ce80b 100644 --- a/pubspec.yaml +++ b/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