diff --git a/README.md b/README.md index b806848..84d6aab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# weiman v1.0.4 +# weiman v1.0.5 ### 微漫脱敏后的开源代码 diff --git a/lib/activities/book.dart b/lib/activities/book.dart index 66984e3..3872f9b 100644 --- a/lib/activities/book.dart +++ b/lib/activities/book.dart @@ -11,8 +11,9 @@ class ActivityBook extends StatefulWidget { } class BookState extends State { + static BoxDecoration _border; final GlobalKey _refresh = GlobalKey(); - GlobalKey _key = GlobalKey(); + ScrollController _scrollController; bool _reverse = false; bool isFavorite = false; @@ -27,6 +28,13 @@ class BookState extends State { SchedulerBinding.instance.addPostFrameCallback((_) { _refresh.currentState.show(); }); + _scrollController = ScrollController(); + } + + @override + dispose() { + _scrollController.dispose(); + super.dispose(); } Future loadBook() async { @@ -64,7 +72,7 @@ class BookState extends State { final history = book.chapters .firstWhere((chapter) => chapter.cid == book.history.cid); SchedulerBinding.instance.addPostFrameCallback((_) { - _key.currentState.currentInnerPosition.animateTo( + _scrollController.animateTo( WidgetChapter.height * chapters.indexOf(history).toDouble(), duration: Duration(milliseconds: 500), curve: Curves.linear); @@ -93,70 +101,6 @@ class BookState extends State { }); } - List _headerBuilder(BuildContext context, bool innerBoxIsScrolled) { - Color color = isFavorite ? Colors.red : Colors.white; - IconData icon = isFavorite ? Icons.favorite : Icons.favorite_border; - final book = this.book ?? widget.book; - return [ - SliverAppBar( - floating: true, - pinned: true, - snap: false, - title: Text(widget.book.name), - expandedHeight: 200, - actions: [ - IconButton( - onPressed: _sort, - icon: Icon(_reverse - ? FontAwesomeIcons.sortNumericDown - : FontAwesomeIcons.sortNumericDownAlt)), - IconButton(onPressed: favoriteBook, icon: Icon(icon, color: color)) - ], - flexibleSpace: FlexibleSpaceBar( - background: SafeArea( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: - EdgeInsets.only(top: 50, left: 20, right: 10, bottom: 20), - height: 160, - child: Hero( - tag: widget.heroTag, - child: Image.network( - widget.book.avatar, - ), - ), - ), - Expanded( - child: Container( - padding: EdgeInsets.only(top: 50, right: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '作者:' + (book.author ?? ''), - style: TextStyle(color: Colors.white), - ), - Container( - margin: EdgeInsets.only(top: 10), - ), - Text( - '简介:\n' + (book.description ?? ''), - softWrap: true, - style: TextStyle(color: Colors.white, height: 1.2), - ), - ], - ), - )), - ], - ), - ), - ), - ) - ]; - } - List chapterWidgets() { final book = this.book ?? widget.book; List list = []; @@ -175,6 +119,16 @@ class BookState extends State { 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: _border, + child: WidgetChapter( + chapter: chapter, + onTap: _openChapter, + read: isRead, + ), + ); + } return WidgetChapter( chapter: chapter, onTap: _openChapter, @@ -184,6 +138,10 @@ class BookState extends State { @override Widget build(BuildContext context) { + if (_border == null) + _border = BoxDecoration( + border: Border( + bottom: Divider.createBorderSide(context, color: Colors.grey))); Color color = isFavorite ? Colors.red : Colors.white; IconData icon = isFavorite ? Icons.favorite : Icons.favorite_border; final book = this.book ?? widget.book; @@ -192,140 +150,79 @@ class BookState extends State { key: _refresh, onRefresh: loadBook, maxDragOffset: kToolbarHeight * 2, - child: NestedScrollView( - key: _key, - headerSliverBuilder: (_, __) => [], - physics: AlwaysScrollableClampingScrollPhysics(), - body: CustomScrollView( - physics: AlwaysScrollableClampingScrollPhysics(), - slivers: [ - SliverAppBar( - floating: true, - pinned: false, - title: Text(widget.book.name), - expandedHeight: 200, - actions: [ - IconButton( - onPressed: _sort, - icon: Icon(_reverse - ? FontAwesomeIcons.sortNumericDown - : FontAwesomeIcons.sortNumericDownAlt)), - IconButton( - onPressed: favoriteBook, icon: Icon(icon, color: color)) - ], - flexibleSpace: FlexibleSpaceBar( - background: SafeArea( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: EdgeInsets.only( - top: 50, left: 20, right: 10, bottom: 20), - height: 160, - child: Hero( - tag: widget.heroTag, - child: Image.network( - widget.book.avatar, - ), - ), + child: CustomScrollView( + controller: _scrollController, + slivers: [ + SliverAppBar( + floating: true, + pinned: true, + title: Text(widget.book.name), + expandedHeight: 200, + actions: [ + IconButton( + onPressed: _sort, + icon: Icon(_reverse + ? FontAwesomeIcons.sortNumericDown + : FontAwesomeIcons.sortNumericDownAlt)), + IconButton( + onPressed: favoriteBook, icon: Icon(icon, color: color)) + ], + flexibleSpace: FlexibleSpaceBar( + background: SafeArea( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only( + top: 50, left: 20, right: 10, bottom: 20), + height: 160, + child: Hero( + tag: widget.heroTag, + child: + Image(image: NetworkImageSSL(widget.book.avatar)), ), - Expanded( - child: Container( - padding: EdgeInsets.only(top: 50, right: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '作者:' + (book.author ?? ''), - style: TextStyle(color: Colors.white), - ), - Container( - margin: EdgeInsets.only(top: 10), - ), - Text( - '简介:\n' + (book.description ?? ''), - softWrap: true, - style: - TextStyle(color: Colors.white, height: 1.2), - ), - ], - ), - )), - ], - ), + ), + Expanded( + child: Container( + padding: EdgeInsets.only(top: 50, right: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '作者:' + (book.author ?? ''), + style: TextStyle(color: Colors.white), + ), + Container( + margin: EdgeInsets.only(top: 10), + ), + Text( + '简介:\n' + (book.description ?? ''), + softWrap: true, + style: + TextStyle(color: Colors.white, height: 1.2), + ), + ], + ), + )), + ], ), ), ), - PullToRefreshContainer((info) => SliverPullToRefreshHeader( - info: info, - onTap: () => _refresh.currentState - .show(notificationDragOffset: kToolbarHeight * 2), - )), - NestedScrollViewInnerScrollPositionKeyWidget( - Key('0'), - SliverList( - delegate: SliverChildBuilderDelegate( - buildChapter, - childCount: book.chapters.length, - ), - ), + ), + PullToRefreshContainer((info) => SliverPullToRefreshHeader( + info: info, + onTap: () => _refresh.currentState + .show(notificationDragOffset: kToolbarHeight * 2), + )), + SliverList( + delegate: SliverChildBuilderDelegate( + buildChapter, + childCount: book.chapters.length, ), - ], - ), + ), + ], ), ), ); } - - Widget build1(BuildContext context) { - final double statusBarHeight = MediaQuery.of(context).padding.top; - var pinnedHeaderHeight = - //statusBar height - statusBarHeight + - //pinned SliverAppBar height in header - kToolbarHeight; - - return Scaffold( - body: NestedScrollViewRefreshIndicator( - key: _refresh, - onRefresh: loadBook, - child: NestedScrollView( - key: _key, - pinnedHeaderSliverHeightBuilder: () => pinnedHeaderHeight, - headerSliverBuilder: _headerBuilder, - body: LayoutBuilder( - builder: (_, __) { - if (isLoading) - return Container(); - else if (isSuccess) { - return ListView( - children: ListTile.divideTiles( - context: context, - color: Colors.grey, - tiles: chapterWidgets()) - .toList()); - } - return Container( - constraints: BoxConstraints.expand(), - alignment: Alignment.center, - child: Center( - child: Text( - '读取失败,下拉刷新\n如果多次失败,请检查网络', - textAlign: TextAlign.center, - ), - ), - ); - }, - ), - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - _key.currentState.currentInnerPosition.animateTo(0, - duration: Duration(milliseconds: 100), curve: Curves.linear); - }, - child: Icon(FontAwesomeIcons.angleDoubleUp), - ), - ); - } } diff --git a/lib/activities/chapter.dart b/lib/activities/chapter.dart index c2901f0..45942de 100644 --- a/lib/activities/chapter.dart +++ b/lib/activities/chapter.dart @@ -117,6 +117,12 @@ class _ChapterDrawer extends State { }); } + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + void updateRead() { final readChapter = widget.book.chapters .firstWhere((chapter) => widget.book.history?.cid == chapter.cid); @@ -134,12 +140,6 @@ class _ChapterDrawer extends State { ); } - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { return Drawer( @@ -183,6 +183,9 @@ class ChapterContentView extends StatefulWidget { class _ChapterContentView extends State { final GlobalKey _refresh = GlobalKey(); final List images = []; + TextStyle _style = TextStyle(color: Colors.white); + BoxDecoration _decoration = + BoxDecoration(color: Colors.black.withOpacity(0.4)); int chapterIndex = -1; bool hasNextChapter = false; @@ -223,6 +226,30 @@ class _ChapterContentView extends State { @override Widget build(BuildContext context) { + final List list = []; + for (var i = 0; i < images.length; i++) { + list.add(SliverStickyHeader( + overlapsContent: true, + header: SafeArea( + top: true, + bottom: false, + child: Row( + children: [ + Container( + padding: EdgeInsets.all(5), + decoration: _decoration, + child: Text( + '${i + 1} / ${images.length}', + style: _style, + ), + ), + ], + ), + ), + sliver: + SliverToBoxAdapter(child: Image(image: NetworkImageSSL(images[i]))), + )); + } return PullToRefreshNotification( key: _refresh, onRefresh: fetchImages, @@ -236,16 +263,14 @@ class _ChapterContentView extends State { floating: true, actions: widget.actions, ), - PullToRefreshContainer((info) => SliverPullToRefreshHeader( - info: info, - onTap: () => _refresh.currentState - .show(notificationDragOffset: kToolbarHeight * 2), - )), - SliverList( - delegate: SliverChildBuilderDelegate( - (ctx, i) => Image.network(images[i]), - childCount: images.length), + PullToRefreshContainer( + (info) => SliverPullToRefreshHeader( + info: info, + onTap: () => _refresh.currentState + .show(notificationDragOffset: kToolbarHeight * 2), + ), ), + ...list, ], ), ); diff --git a/lib/activities/home.dart b/lib/activities/home.dart index e647ded..892e8d4 100644 --- a/lib/activities/home.dart +++ b/lib/activities/home.dart @@ -25,6 +25,7 @@ class HomeState extends State { /// 提前检查一次藏书的更新情况 SchedulerBinding.instance.addPostFrameCallback((_) async { + autoSwitchTheme(); _FavoriteList.getBooks(); await _FavoriteList.checkNews(); final updated = _FavoriteList.hasNews.values @@ -39,6 +40,14 @@ class HomeState extends State { }); } + void autoSwitchTheme() async { + final isDark = await DynamicTheme.of(context).loadBrightness(); + final nowIsDark = DynamicTheme.of(context).brightness == Brightness.dark; + if (isDark != nowIsDark) + DynamicTheme.of(context) + .setBrightness(isDark ? Brightness.dark : Brightness.light); + } + void gotoSearch() { Navigator.push( context, @@ -66,9 +75,8 @@ class HomeState extends State { @override Widget build(BuildContext context) { - var media = MediaQuery.of(context); - var width = media.size.width; - width = width * .8; + final media = MediaQuery.of(context); + final width = (media.size.width * 0.8).roundToDouble(); return Scaffold( key: _scaffoldKey, appBar: AppBar( @@ -183,10 +191,12 @@ class HomeState extends State { ), ], ), - Quick( - key: _quickState, - width: width, - draggableModeChanged: _draggableModeChanged, + Center( + child: Quick( + key: _quickState, + width: width, + draggableModeChanged: _draggableModeChanged, + ), ), Container( margin: EdgeInsets.only(bottom: 10), @@ -238,21 +248,3 @@ class HomeState extends State { ); } } - -Iterable favoriteTiles(context, Iterable books, - {void Function(Book book) onTap}) { - return books.map((book) => ListTile( - onTap: () { - onTap(book); - }, - title: Text(book.name), - leading: Image.network(book.avatar), - subtitle: Text( - '作者:' + book.author, - style: TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - )); -} diff --git a/lib/activities/search.dart b/lib/activities/search.dart index af5cce6..2c76937 100644 --- a/lib/activities/search.dart +++ b/lib/activities/search.dart @@ -69,7 +69,12 @@ class SearchState extends State { }, child: TextField( decoration: InputDecoration( - hintText: '搜索书名', prefixIcon: Icon(Icons.search)), + hintText: '搜索书名', + prefixIcon: IconButton( + onPressed: startSearch, + icon: Icon(Icons.search), + ), + ), textAlign: TextAlign.left, controller: _controller, autofocus: true, diff --git a/lib/classes/http.dart b/lib/classes/http.dart index 2f6a3ba..df7f973 100644 --- a/lib/classes/http.dart +++ b/lib/classes/http.dart @@ -1,7 +1,6 @@ part of '../main.dart'; const domain = ''; -final host = Uri.parse(domain).host; class UserAgentClient extends http.BaseClient { final String userAgent; diff --git a/lib/classes/networkImageSSL.dart b/lib/classes/networkImageSSL.dart new file mode 100644 index 0000000..56fe42d --- /dev/null +++ b/lib/classes/networkImageSSL.dart @@ -0,0 +1,124 @@ +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; + +/// The dart:io implementation of [image_provider.NetworkImage]. +class NetworkImageSSL + extends image_provider.ImageProvider + implements image_provider.NetworkImage { + /// 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), + assert(scale != null); + + @override + final String url; + + @override + final double scale; + + @override + final Map headers; + + @override + Future obtainKey( + image_provider.ImageConfiguration configuration) { + return SynchronousFuture(this); + } + + @override + image_provider.ImageStreamCompleter load( + image_provider.NetworkImage key, image_provider.DecoderCallback decode) { + // Ownership of this controller is handed off to [_loadAsync]; it is that + // method's responsibility to close the controller's stream when the image + // has been loaded or an error is thrown. + final StreamController chunkEvents = + StreamController(); + + return image_provider.MultiFrameImageStreamCompleter( + codec: _loadAsync(key, chunkEvents, decode), + chunkEvents: chunkEvents.stream, + scale: key.scale, + informationCollector: () { + return [ + DiagnosticsProperty( + 'Image provider', this), + DiagnosticsProperty('Image key', key), + ]; + }, + ); + } + + // Do not access this field directly; use [_httpClient] instead. + // We set `autoUncompress` to false to ensure that we can trust the value of + // the `Content-Length` HTTP header. We automatically uncompress the content + // in our call to [consolidateHttpClientResponseBytes]. + static final HttpClient _sharedHttpClient = HttpClient() + ..autoUncompress = false + ..badCertificateCallback = (_, __, ___) => true; + + static HttpClient get _httpClient { + HttpClient client = _sharedHttpClient; + assert(() { + if (image_provider.debugNetworkImageHttpClientProvider != null) + client = image_provider.debugNetworkImageHttpClientProvider(); + return true; + }()); + return client; + } + + Future _loadAsync( + NetworkImageSSL key, + StreamController chunkEvents, + image_provider.DecoderCallback decode, + ) async { + try { + assert(key == this); + + final Uri resolved = Uri.base.resolve(key.url); + final HttpClientRequest request = await _httpClient.getUrl(resolved); + headers?.forEach((String name, String value) { + request.headers.add(name, value); + }); + final HttpClientResponse response = await request.close(); + if (response.statusCode != HttpStatus.ok) + throw image_provider.NetworkImageLoadException( + statusCode: response.statusCode, uri: resolved); + + final Uint8List bytes = await consolidateHttpClientResponseBytes( + response, + onBytesReceived: (int cumulative, int total) { + chunkEvents.add(image_provider.ImageChunkEvent( + cumulativeBytesLoaded: cumulative, + expectedTotalBytes: total, + )); + }, + ); + if (bytes.lengthInBytes == 0) + throw Exception('NetworkImage is an empty file: $resolved'); + + return decode(bytes); + } finally { + chunkEvents.close(); + } + } + + @override + bool operator ==(dynamic other) { + if (other.runtimeType != runtimeType) return false; + final NetworkImageSSL typedOther = other; + return url == typedOther.url && scale == typedOther.scale; + } + + @override + int get hashCode => ui.hashValues(url, scale); + + @override + String toString() => '$runtimeType("$url", scale: $scale)'; +} diff --git a/lib/main.dart b/lib/main.dart index 5b7815f..6f69b90 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,7 +8,6 @@ import 'package:async/async.dart'; import 'package:draggable_container/draggable_container.dart'; import 'package:dynamic_theme/dynamic_theme.dart'; import 'package:encrypt/encrypt.dart' as encrypt; -import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/observer.dart'; import 'package:flutter/material.dart' hide NestedScrollView; @@ -26,6 +25,7 @@ import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart' hide CircularProgressIndicator; import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'classes/networkImageSSL.dart'; part './activities/book.dart'; @@ -85,15 +85,6 @@ void main() async { UserAgentClient.init( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'); runApp(Main(packageInfo: packageInfo)); -// runApp(MaterialApp( -// title: '微漫', -// theme: ThemeData.light(), -// darkTheme: ThemeData.dark(), -// themeMode: ThemeMode.system, -// debugShowCheckedModeBanner: false, -// navigatorObservers: [observer], -// home: ActivityHome(packageInfo), -// )); } class Main extends StatefulWidget { @@ -106,24 +97,10 @@ class Main extends StatefulWidget { } class _Main extends State
with WidgetsBindingObserver { - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - @override Widget build(BuildContext context) { return DynamicTheme( - defaultBrightness: ThemeMode.system == ThemeMode.light - ? Brightness.light - : Brightness.dark, + defaultBrightness: Brightness.dark, data: (brightness) => new ThemeData( brightness: brightness, ), @@ -148,10 +125,4 @@ class _Main extends State
with WidgetsBindingObserver { // home: ActivityHome(widget.packageInfo), // ); } - - @override - void didChangePlatformBrightness() { - print('改变亮度'); - setState(() {}); - } } diff --git a/lib/widgets/book.dart b/lib/widgets/book.dart index 8d89a02..bd942b3 100644 --- a/lib/widgets/book.dart +++ b/lib/widgets/book.dart @@ -29,8 +29,8 @@ class WidgetBook extends StatelessWidget { dense: true, leading: Hero( tag: 'bookAvatar${book.aid}', - child: Image.network( - book.avatar, + child: Image(image:NetworkImageSSL( + book.avatar), height: 200, fit: BoxFit.scaleDown, )), @@ -48,7 +48,7 @@ class WidgetBook extends StatelessWidget { } class WidgetChapter extends StatelessWidget { - static final double height = 56; + static final double height = kToolbarHeight; final Chapter chapter; final Function(Chapter) onTap; final bool read; @@ -83,8 +83,8 @@ class WidgetChapter extends StatelessWidget { softWrap: true, maxLines: 2, ), - leading: Image.network( - chapter.avatar, + leading: Image(image:NetworkImageSSL( + chapter.avatar), fit: BoxFit.fitWidth, width: 100, ), @@ -106,8 +106,8 @@ class WidgetHistory extends StatelessWidget { if (onTap != null) onTap(book); }, title: Text(book.name), - leading: Image.network( - book.avatar, + leading: Image(image:NetworkImageSSL( + book.avatar), fit: BoxFit.fitHeight, ), subtitle: Text(book.history.cname), @@ -173,7 +173,7 @@ class _WidgetBookCheckNew extends State { openBook(context, widget.book, 'checkBook${widget.book.aid}'), leading: Hero( tag: 'checkBook${widget.book.aid}', - child: Image.network(widget.book.avatar), + child: Image(image:NetworkImageSSL(widget.book.avatar)), ), dense: true, isThreeLine: true, diff --git a/lib/widgets/favorites.dart b/lib/widgets/favorites.dart index 53627ee..ce57d94 100644 --- a/lib/widgets/favorites.dart +++ b/lib/widgets/favorites.dart @@ -174,7 +174,7 @@ class FBookItem extends StatelessWidget { Widget build(BuildContext context) { return ListTile( onTap: () => onTap(book), - leading: Hero(tag: 'fb ${book.aid}', child: Image.network(book.avatar)), + leading: Hero(tag: 'fb ${book.aid}', child: Image(image:NetworkImageSSL(book.avatar))), 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 a21d6b1..1a64e90 100644 --- a/lib/widgets/quick.dart +++ b/lib/widgets/quick.dart @@ -21,7 +21,7 @@ class QuickBook extends DraggableItem { children: [ Hero( tag: '$heroTag ${book.aid}', - child: Image.network(book.avatar), + child: Image(image: NetworkImageSSL(book.avatar)), ), Positioned( left: 0, @@ -79,7 +79,7 @@ class QuickState extends State { .where((book) => !id.contains(book.aid)) .map((book) => ListTile( title: Text(book.name), - leading: Image.network(book.avatar), + leading: Image(image: NetworkImageSSL(book.avatar)), onTap: () { Navigator.pop(context, book); }, @@ -144,8 +144,6 @@ class QuickState extends State { } int length() { -// print(_key.currentState.items); -// return 0; return _key.currentState.items.where((item) => item is QuickBook).length; } @@ -165,6 +163,8 @@ class QuickState extends State { @override Widget build(BuildContext context) { + final width = widget.width / 4 - 10; + final height = (width / 0.7).roundToDouble(); return Column( children: [ Container( @@ -176,39 +176,36 @@ class QuickState extends State { style: TextStyle(color: Colors.grey, fontSize: 12), ), ), - Container( - width: widget.width, - child: DraggableContainer( - key: _key, - slotMargin: EdgeInsets.only(bottom: 8, left: 7, right: 7), - slotSize: Size(72, 100), - slotDecoration: - BoxDecoration(color: Colors.grey.withOpacity(0.3)), - dragDecoration: BoxDecoration( - boxShadow: [BoxShadow(color: Colors.black, blurRadius: 10)]), - items: _draggableItems, - onDraggableModeChanged: widget.draggableModeChanged, - onChanged: (List items) { - id.clear(); - items.forEach((item) { - if (item is QuickBook) id.add(item.book.aid); - }); - Data.addQuickAll(id); - final nullIndex = items.indexOf(null); - final buttonIndex = items.indexOf(_addButton); - print('null $nullIndex, button $buttonIndex'); - if (nullIndex > -1 && buttonIndex == -1) { - _key.currentState.insteadOfIndex(nullIndex, _addButton, - triggerEvent: false); - } else if (nullIndex > -1 && - buttonIndex > -1 && - nullIndex < buttonIndex) { - _key.currentState.removeItem(_addButton); - _key.currentState.insteadOfIndex(nullIndex, _addButton, - triggerEvent: false); - } - }, - )), + DraggableContainer( + key: _key, + slotMargin: EdgeInsets.only(bottom: 8, left: 6, right: 6), + slotSize: Size(width, height), + slotDecoration: BoxDecoration(color: Colors.grey.withOpacity(0.3)), + dragDecoration: BoxDecoration( + boxShadow: [BoxShadow(color: Colors.black, blurRadius: 10)]), + items: _draggableItems, + onDraggableModeChanged: widget.draggableModeChanged, + onChanged: (List items) { + id.clear(); + items.forEach((item) { + if (item is QuickBook) id.add(item.book.aid); + }); + Data.addQuickAll(id); + final nullIndex = items.indexOf(null); + final buttonIndex = items.indexOf(_addButton); + print('null $nullIndex, button $buttonIndex'); + if (nullIndex > -1 && buttonIndex == -1) { + _key.currentState + .insteadOfIndex(nullIndex, _addButton, triggerEvent: false); + } else if (nullIndex > -1 && + buttonIndex > -1 && + nullIndex < buttonIndex) { + _key.currentState.removeItem(_addButton); + _key.currentState + .insteadOfIndex(nullIndex, _addButton, triggerEvent: false); + } + }, + ), ], ); }