import 'dart:async'; import 'dart:typed_data'; import 'package:chat/generated/i18n.dart'; import 'package:chat/photo/delegate/loading_delegate.dart'; import 'package:chat/photo/engine/lru_cache.dart'; import 'package:chat/photo/engine/throttle.dart'; import 'package:chat/photo/entity/options.dart'; import 'package:chat/photo/provider/asset_provider.dart'; import 'package:chat/photo/provider/config_provider.dart'; import 'package:chat/photo/provider/selected_provider.dart'; import 'package:chat/photo/ui/page/photo_folder_select_menu.dart'; import 'package:chat/photo/ui/page/photo_preview_page.dart'; import 'package:chat/utils/screen.dart'; import 'package:flutter/material.dart'; import 'package:photo_manager/photo_manager.dart'; part './bottom_widget.dart'; part './image_item.dart'; class PhotoMainPage extends StatefulWidget { final ValueChanged> onClose; final Options options; final List photoList; const PhotoMainPage({ Key key, this.onClose, this.options, this.photoList, }) : super(key: key); @override _PhotoMainPageState createState() => _PhotoMainPageState(); } class _PhotoMainPageState extends State with SelectedProvider { Options get options => widget.options; AssetProvider get assetProvider => PhotoPickerProvider.of(context).assetProvider; List get list => assetProvider.data; Color get themeColor => options.themeColor; AssetPathEntity _currentPath; bool _isInit = false; AssetPathEntity get currentPath { if (_currentPath == null) { return null; } return _currentPath; } set currentPath(AssetPathEntity value) { _currentPath = value; } String get currentGalleryName { if (currentPath?.isAll == true) { return I18n.of(context).all_photo; } return currentPath?.name ?? "Select Folder"; } GlobalKey scaffoldKey; ScrollController scrollController; bool isPushed = false; bool get useAlbum => widget.photoList == null || widget.photoList.isEmpty; Throttle _changeThrottle; var appBarKey = GlobalKey(); OverlayEntry overlayEntry; bool isQuit = false; @override void initState() { super.initState(); scaffoldKey = GlobalKey(); scrollController = ScrollController(); _changeThrottle = Throttle(onCall: _onAssetChange); PhotoManager.addChangeCallback(_changeThrottle.call); PhotoManager.startChangeNotify(); _refreshListFromGallery(); } @override void dispose() { PhotoManager.removeChangeCallback(_changeThrottle.call); PhotoManager.stopChangeNotify(); _changeThrottle.dispose(); scaffoldKey = null; appBarKey = null; super.dispose(); } @override Widget build(BuildContext context) { var textStyle = TextStyle( color: options.textColor, fontSize: 14.0, ); return Theme( data: Theme.of(context).copyWith(primaryColor: options.themeColor), child: DefaultTextStyle( style: textStyle, child: Scaffold( backgroundColor: Color(0xFFF0F0F0), appBar: AppBar( elevation: 1.0, key: appBarKey, backgroundColor: Colors.white, leading: IconButton( icon: fixedText( I18n.of(context).cancel, color: options.textColor, ), onPressed: _cancel, ), title: PhotoSelectMenu( parentKey: appBarKey, onSelectFolder: _onGalleryChange), centerTitle: true, actions: [ Center( child:Padding(padding: EdgeInsets.only(right:16),child: fixedText('$selectedCount/${options.maxSelected}',color: Colors.blue,fontSize: 16)) ) ], ), body: _buildBody(), bottomNavigationBar: _BottomWidget( key: scaffoldKey, options: options, galleryName: currentGalleryName, onSend: selectedList.isEmpty ? null : sure, onTapPreview: selectedList.isEmpty ? null : _onTapPreview, selectedProvider: this, ), ), ), ); } void _cancel() { selectedList.clear(); widget.onClose(selectedList); } @override bool isUpperLimit() { var result = selectedCount == options.maxSelected; if (result) _showTip(I18n.of(context) .have_select .replaceFirst('/s1', options.maxSelected.toString())); return result; } void sure() { widget.onClose?.call(selectedList); } void _showTip(String msg) { if (isPushed) { return; } Scaffold.of(scaffoldKey.currentContext).showSnackBar( SnackBar( content: Text( msg, style: TextStyle( color: options.textColor, fontSize: 15.0, ), ), duration: Duration(milliseconds: 1500), backgroundColor: themeColor.withOpacity(0.8), ), ); } Future _refreshListFromGallery() async { List pathList; switch (options.pickType) { case PickType.onlyImage: pathList = await PhotoManager.getAssetPathList(type: RequestType.image); break; case PickType.onlyVideo: pathList = await PhotoManager.getAssetPathList(type: RequestType.video); break; default: pathList = await PhotoManager.getAssetPathList(); } if (pathList == null) { return; } options.sortDelegate.sort(pathList); if (pathList.isNotEmpty) { assetProvider.current = pathList[0]; await assetProvider.loadMore(); } for (var path in pathList) { if (path.isAll) { path.name = I18n.of(context).all_photo; } } setState(() { _isInit = true; }); } Widget _buildBody() { if (!_isInit) { return Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(themeColor), )); } final noMore = assetProvider.noMore; final count = assetProvider.count + (noMore ? 0 : 1); if (list.length == 0) { print('图片数目为空'); _refreshListFromGallery(); return Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(themeColor), )); } print('图片数目${list.length}'); return Container( color: options.dividerColor, child: GridView.builder( controller: scrollController, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: options.rowCount, childAspectRatio: options.itemRadio, crossAxisSpacing: options.padding, mainAxisSpacing: options.padding, ), itemBuilder: _buildItem, itemCount: count, ), ); } Widget _buildItem(BuildContext context, int index) { final noMore = assetProvider.noMore; if (!noMore && index == assetProvider.count) { _loadMore(); return _buildLoading(); } var data = list[index]; return RepaintBoundary( child: GestureDetector( onTap: () => _onItemClick(data, index), child: Stack( children: [ ImageItem( entity: data, themeColor: themeColor, size: options.thumbSize, loadingDelegate: options.loadingDelegate, ), _buildMask(containsEntity(data)), _buildSelected(data), ], ), ), ); } _loadMore() async { await assetProvider.loadMore(); setState(() {}); } _buildMask(bool showMask) { return IgnorePointer( child: AnimatedContainer( color: showMask ? Colors.black.withOpacity(0.5) : Colors.transparent, duration: Duration(milliseconds: 300), ), ); } Widget _buildSelected(AssetEntity entity) { var currentSelected = containsEntity(entity); return Positioned( right: 0.0, width: 40.0, height: 40.0, child: GestureDetector( onTap: () { changeCheck(!currentSelected, entity); }, behavior: HitTestBehavior.translucent, child: _buildText(entity), ), ); } Widget _buildText(AssetEntity entity) { var isSelected = containsEntity(entity); Widget child; BoxDecoration decoration; if (isSelected) { child = Text( (indexOfSelected(entity) + 1).toString(), textAlign: TextAlign.center, style: TextStyle( fontSize: 12.0, color: Colors.white, ), ); decoration = BoxDecoration( color: Color(0xFF2D81FF), shape: BoxShape.circle, border: Border.all( color: Colors.white30, ), ); } else { decoration = BoxDecoration( shape: BoxShape.circle, border: Border.all( color: themeColor, ), boxShadow: [BoxShadow(color: Colors.black38)], color: Colors.white24); } return Padding( padding: const EdgeInsets.all(8.0), child: AnimatedContainer( duration: Duration(milliseconds: 300), decoration: decoration, alignment: Alignment.center, child: child, ), ); } void changeCheck(bool value, AssetEntity entity) { if (value) { addSelectEntity(entity); } else { removeSelectEntity(entity); } setState(() {}); } void _onGalleryChange(AssetPathEntity assetPathEntity) async { if (assetPathEntity != assetProvider.current) { assetProvider.current = assetPathEntity; await assetProvider.loadMore(); setState(() {}); } } void _onItemClick(AssetEntity data, int index) { var result = new PhotoPreviewResult(); isPushed = true; Navigator.of(context).push( MaterialPageRoute( builder: (ctx) { return PhotoPickerProvider( options: options, child: PhotoPreviewPage( selectedProvider: this, list: List.of(list), initIndex: index, changeProviderOnCheckChange: true, result: result, isPreview: false, assetProvider: assetProvider, ), ); }, ), ).then((v) { if (handlePreviewResult(v)) { Navigator.pop(context, v); return; } isPushed = false; setState(() {}); }); } void _onTapPreview() async { var result = new PhotoPreviewResult(); isPushed = true; var v = await Navigator.of(context).push( MaterialPageRoute( builder: (ctx) => PhotoPickerProvider( options: options, child: PhotoPreviewPage( selectedProvider: this, list: List.of(selectedList), changeProviderOnCheckChange: false, result: result, isPreview: true, assetProvider: assetProvider, ), ), ), ); if (handlePreviewResult(v)) { // print(v); Navigator.pop(context, v); return; } isPushed = false; compareAndRemoveEntities(result.previewSelectedList); } bool handlePreviewResult(List v) { if (v == null) { return false; } if (v is List) { return true; } return false; } Widget _buildLoading() { return Center( child: Column( children: [ Container( width: 40.0, height: 40.0, padding: const EdgeInsets.all(5.0), child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(themeColor), ), ), Padding( padding: const EdgeInsets.all(8.0), child: Text( I18n.of(context).loading, style: const TextStyle( fontSize: 12.0, ), ), ), ], crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, ), ); } void _onAssetChange() { if (useAlbum) { _onPhotoRefresh(); } } void _onPhotoRefresh() async { List pathList; switch (options.pickType) { case PickType.onlyImage: pathList = await PhotoManager.getAssetPathList(type: RequestType.image); break; case PickType.onlyVideo: pathList = await PhotoManager.getAssetPathList(type: RequestType.video); break; default: pathList = await PhotoManager.getAssetPathList(); } if (pathList == null) { return; } // Not deleted _onGalleryChange(this.currentPath); } }