Hibok
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

496 lines
12 KiB

  1. import 'dart:async';
  2. import 'dart:typed_data';
  3. import 'package:chat/generated/i18n.dart';
  4. import 'package:chat/photo/delegate/loading_delegate.dart';
  5. import 'package:chat/photo/engine/lru_cache.dart';
  6. import 'package:chat/photo/engine/throttle.dart';
  7. import 'package:chat/photo/entity/options.dart';
  8. import 'package:chat/photo/provider/asset_provider.dart';
  9. import 'package:chat/photo/provider/config_provider.dart';
  10. import 'package:chat/photo/provider/selected_provider.dart';
  11. import 'package:chat/photo/ui/page/photo_folder_select_menu.dart';
  12. import 'package:chat/photo/ui/page/photo_preview_page.dart';
  13. import 'package:chat/utils/screen.dart';
  14. import 'package:flutter/material.dart';
  15. import 'package:photo_manager/photo_manager.dart';
  16. part './bottom_widget.dart';
  17. part './image_item.dart';
  18. class PhotoMainPage extends StatefulWidget {
  19. final ValueChanged<List<AssetEntity>> onClose;
  20. final Options options;
  21. final List<AssetPathEntity> photoList;
  22. const PhotoMainPage({
  23. Key key,
  24. this.onClose,
  25. this.options,
  26. this.photoList,
  27. }) : super(key: key);
  28. @override
  29. _PhotoMainPageState createState() => _PhotoMainPageState();
  30. }
  31. class _PhotoMainPageState extends State<PhotoMainPage> with SelectedProvider {
  32. Options get options => widget.options;
  33. AssetProvider get assetProvider =>
  34. PhotoPickerProvider.of(context).assetProvider;
  35. List<AssetEntity> get list => assetProvider.data;
  36. Color get themeColor => options.themeColor;
  37. AssetPathEntity _currentPath;
  38. bool _isInit = false;
  39. AssetPathEntity get currentPath {
  40. if (_currentPath == null) {
  41. return null;
  42. }
  43. return _currentPath;
  44. }
  45. set currentPath(AssetPathEntity value) {
  46. _currentPath = value;
  47. }
  48. String get currentGalleryName {
  49. if (currentPath?.isAll == true) {
  50. return I18n.of(context).all_photo;
  51. }
  52. return currentPath?.name ?? "Select Folder";
  53. }
  54. GlobalKey scaffoldKey;
  55. ScrollController scrollController;
  56. bool isPushed = false;
  57. bool get useAlbum => widget.photoList == null || widget.photoList.isEmpty;
  58. Throttle _changeThrottle;
  59. var appBarKey = GlobalKey();
  60. OverlayEntry overlayEntry;
  61. bool isQuit = false;
  62. @override
  63. void initState() {
  64. super.initState();
  65. scaffoldKey = GlobalKey();
  66. scrollController = ScrollController();
  67. _changeThrottle = Throttle(onCall: _onAssetChange);
  68. PhotoManager.addChangeCallback(_changeThrottle.call);
  69. PhotoManager.startChangeNotify();
  70. _refreshListFromGallery();
  71. }
  72. @override
  73. void dispose() {
  74. PhotoManager.removeChangeCallback(_changeThrottle.call);
  75. PhotoManager.stopChangeNotify();
  76. _changeThrottle.dispose();
  77. scaffoldKey = null;
  78. appBarKey = null;
  79. super.dispose();
  80. }
  81. @override
  82. Widget build(BuildContext context) {
  83. var textStyle = TextStyle(
  84. color: options.textColor,
  85. fontSize: 14.0,
  86. );
  87. return Theme(
  88. data: Theme.of(context).copyWith(primaryColor: options.themeColor),
  89. child: DefaultTextStyle(
  90. style: textStyle,
  91. child: Scaffold(
  92. backgroundColor: Color(0xFFF0F0F0),
  93. appBar: AppBar(
  94. elevation: 1.0,
  95. key: appBarKey,
  96. backgroundColor: Colors.white,
  97. leading: IconButton(
  98. icon: fixedText(
  99. I18n.of(context).cancel,
  100. color: options.textColor,
  101. ),
  102. onPressed: _cancel,
  103. ),
  104. title: PhotoSelectMenu(
  105. parentKey: appBarKey, onSelectFolder: _onGalleryChange),
  106. centerTitle: true,
  107. actions: <Widget>[
  108. Center(
  109. child:Padding(padding: EdgeInsets.only(right:16),child: fixedText('$selectedCount/${options.maxSelected}',color: Colors.blue,fontSize: 16))
  110. )
  111. ],
  112. ),
  113. body: _buildBody(),
  114. bottomNavigationBar: _BottomWidget(
  115. key: scaffoldKey,
  116. options: options,
  117. galleryName: currentGalleryName,
  118. onSend: selectedList.isEmpty ? null : sure,
  119. onTapPreview: selectedList.isEmpty ? null : _onTapPreview,
  120. selectedProvider: this,
  121. ),
  122. ),
  123. ),
  124. );
  125. }
  126. void _cancel() {
  127. selectedList.clear();
  128. widget.onClose(selectedList);
  129. }
  130. @override
  131. bool isUpperLimit() {
  132. var result = selectedCount == options.maxSelected;
  133. if (result)
  134. _showTip(I18n.of(context)
  135. .have_select
  136. .replaceFirst('/s1', options.maxSelected.toString()));
  137. return result;
  138. }
  139. void sure() {
  140. widget.onClose?.call(selectedList);
  141. }
  142. void _showTip(String msg) {
  143. if (isPushed) {
  144. return;
  145. }
  146. Scaffold.of(scaffoldKey.currentContext).showSnackBar(
  147. SnackBar(
  148. content: Text(
  149. msg,
  150. style: TextStyle(
  151. color: options.textColor,
  152. fontSize: 15.0,
  153. ),
  154. ),
  155. duration: Duration(milliseconds: 1500),
  156. backgroundColor: themeColor.withOpacity(0.8),
  157. ),
  158. );
  159. }
  160. Future<void> _refreshListFromGallery() async {
  161. List<AssetPathEntity> pathList;
  162. switch (options.pickType) {
  163. case PickType.onlyImage:
  164. pathList = await PhotoManager.getAssetPathList(type: RequestType.image);
  165. break;
  166. case PickType.onlyVideo:
  167. pathList = await PhotoManager.getAssetPathList(type: RequestType.video);
  168. break;
  169. default:
  170. pathList = await PhotoManager.getAssetPathList();
  171. }
  172. if (pathList == null) {
  173. return;
  174. }
  175. options.sortDelegate.sort(pathList);
  176. if (pathList.isNotEmpty) {
  177. assetProvider.current = pathList[0];
  178. await assetProvider.loadMore();
  179. }
  180. for (var path in pathList) {
  181. if (path.isAll) {
  182. path.name = I18n.of(context).all_photo;
  183. }
  184. }
  185. setState(() {
  186. _isInit = true;
  187. });
  188. }
  189. Widget _buildBody() {
  190. if (!_isInit) {
  191. return Center(
  192. child: CircularProgressIndicator(
  193. valueColor: AlwaysStoppedAnimation(themeColor),
  194. ));
  195. }
  196. final noMore = assetProvider.noMore;
  197. final count = assetProvider.count + (noMore ? 0 : 1);
  198. if (list.length == 0) {
  199. print('图片数目为空');
  200. _refreshListFromGallery();
  201. return Center(
  202. child: CircularProgressIndicator(
  203. valueColor: AlwaysStoppedAnimation(themeColor),
  204. ));
  205. }
  206. print('图片数目${list.length}');
  207. return Container(
  208. color: options.dividerColor,
  209. child: GridView.builder(
  210. controller: scrollController,
  211. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  212. crossAxisCount: options.rowCount,
  213. childAspectRatio: options.itemRadio,
  214. crossAxisSpacing: options.padding,
  215. mainAxisSpacing: options.padding,
  216. ),
  217. itemBuilder: _buildItem,
  218. itemCount: count,
  219. ),
  220. );
  221. }
  222. Widget _buildItem(BuildContext context, int index) {
  223. final noMore = assetProvider.noMore;
  224. if (!noMore && index == assetProvider.count) {
  225. _loadMore();
  226. return _buildLoading();
  227. }
  228. var data = list[index];
  229. return RepaintBoundary(
  230. child: GestureDetector(
  231. onTap: () => _onItemClick(data, index),
  232. child: Stack(
  233. children: <Widget>[
  234. ImageItem(
  235. entity: data,
  236. themeColor: themeColor,
  237. size: options.thumbSize,
  238. loadingDelegate: options.loadingDelegate,
  239. ),
  240. _buildMask(containsEntity(data)),
  241. _buildSelected(data),
  242. ],
  243. ),
  244. ),
  245. );
  246. }
  247. _loadMore() async {
  248. await assetProvider.loadMore();
  249. setState(() {});
  250. }
  251. _buildMask(bool showMask) {
  252. return IgnorePointer(
  253. child: AnimatedContainer(
  254. color: showMask ? Colors.black.withOpacity(0.5) : Colors.transparent,
  255. duration: Duration(milliseconds: 300),
  256. ),
  257. );
  258. }
  259. Widget _buildSelected(AssetEntity entity) {
  260. var currentSelected = containsEntity(entity);
  261. return Positioned(
  262. right: 0.0,
  263. width: 40.0,
  264. height: 40.0,
  265. child: GestureDetector(
  266. onTap: () {
  267. changeCheck(!currentSelected, entity);
  268. },
  269. behavior: HitTestBehavior.translucent,
  270. child: _buildText(entity),
  271. ),
  272. );
  273. }
  274. Widget _buildText(AssetEntity entity) {
  275. var isSelected = containsEntity(entity);
  276. Widget child;
  277. BoxDecoration decoration;
  278. if (isSelected) {
  279. child = Text(
  280. (indexOfSelected(entity) + 1).toString(),
  281. textAlign: TextAlign.center,
  282. style: TextStyle(
  283. fontSize: 12.0,
  284. color: Colors.white,
  285. ),
  286. );
  287. decoration = BoxDecoration(
  288. color: Color(0xFF2D81FF),
  289. shape: BoxShape.circle,
  290. border: Border.all(
  291. color: Colors.white30,
  292. ),
  293. );
  294. } else {
  295. decoration = BoxDecoration(
  296. shape: BoxShape.circle,
  297. border: Border.all(
  298. color: themeColor,
  299. ),
  300. boxShadow: [BoxShadow(color: Colors.black38)],
  301. color: Colors.white24);
  302. }
  303. return Padding(
  304. padding: const EdgeInsets.all(8.0),
  305. child: AnimatedContainer(
  306. duration: Duration(milliseconds: 300),
  307. decoration: decoration,
  308. alignment: Alignment.center,
  309. child: child,
  310. ),
  311. );
  312. }
  313. void changeCheck(bool value, AssetEntity entity) {
  314. if (value) {
  315. addSelectEntity(entity);
  316. } else {
  317. removeSelectEntity(entity);
  318. }
  319. setState(() {});
  320. }
  321. void _onGalleryChange(AssetPathEntity assetPathEntity) async {
  322. if (assetPathEntity != assetProvider.current) {
  323. assetProvider.current = assetPathEntity;
  324. await assetProvider.loadMore();
  325. setState(() {});
  326. }
  327. }
  328. void _onItemClick(AssetEntity data, int index) {
  329. var result = new PhotoPreviewResult();
  330. isPushed = true;
  331. Navigator.of(context).push(
  332. MaterialPageRoute(
  333. builder: (ctx) {
  334. return PhotoPickerProvider(
  335. options: options,
  336. child: PhotoPreviewPage(
  337. selectedProvider: this,
  338. list: List.of(list),
  339. initIndex: index,
  340. changeProviderOnCheckChange: true,
  341. result: result,
  342. isPreview: false,
  343. assetProvider: assetProvider,
  344. ),
  345. );
  346. },
  347. ),
  348. ).then((v) {
  349. if (handlePreviewResult(v)) {
  350. Navigator.pop(context, v);
  351. return;
  352. }
  353. isPushed = false;
  354. setState(() {});
  355. });
  356. }
  357. void _onTapPreview() async {
  358. var result = new PhotoPreviewResult();
  359. isPushed = true;
  360. var v = await Navigator.of(context).push(
  361. MaterialPageRoute(
  362. builder: (ctx) => PhotoPickerProvider(
  363. options: options,
  364. child: PhotoPreviewPage(
  365. selectedProvider: this,
  366. list: List.of(selectedList),
  367. changeProviderOnCheckChange: false,
  368. result: result,
  369. isPreview: true,
  370. assetProvider: assetProvider,
  371. ),
  372. ),
  373. ),
  374. );
  375. if (handlePreviewResult(v)) {
  376. // print(v);
  377. Navigator.pop(context, v);
  378. return;
  379. }
  380. isPushed = false;
  381. compareAndRemoveEntities(result.previewSelectedList);
  382. }
  383. bool handlePreviewResult(List<AssetEntity> v) {
  384. if (v == null) {
  385. return false;
  386. }
  387. if (v is List<AssetEntity>) {
  388. return true;
  389. }
  390. return false;
  391. }
  392. Widget _buildLoading() {
  393. return Center(
  394. child: Column(
  395. children: <Widget>[
  396. Container(
  397. width: 40.0,
  398. height: 40.0,
  399. padding: const EdgeInsets.all(5.0),
  400. child: CircularProgressIndicator(
  401. valueColor: AlwaysStoppedAnimation(themeColor),
  402. ),
  403. ),
  404. Padding(
  405. padding: const EdgeInsets.all(8.0),
  406. child: Text(
  407. I18n.of(context).loading,
  408. style: const TextStyle(
  409. fontSize: 12.0,
  410. ),
  411. ),
  412. ),
  413. ],
  414. crossAxisAlignment: CrossAxisAlignment.center,
  415. mainAxisAlignment: MainAxisAlignment.center,
  416. ),
  417. );
  418. }
  419. void _onAssetChange() {
  420. if (useAlbum) {
  421. _onPhotoRefresh();
  422. }
  423. }
  424. void _onPhotoRefresh() async {
  425. List<AssetPathEntity> pathList;
  426. switch (options.pickType) {
  427. case PickType.onlyImage:
  428. pathList = await PhotoManager.getAssetPathList(type: RequestType.image);
  429. break;
  430. case PickType.onlyVideo:
  431. pathList = await PhotoManager.getAssetPathList(type: RequestType.video);
  432. break;
  433. default:
  434. pathList = await PhotoManager.getAssetPathList();
  435. }
  436. if (pathList == null) {
  437. return;
  438. }
  439. // Not deleted
  440. _onGalleryChange(this.currentPath);
  441. }
  442. }