Ei kuvausta

photo_main_page.dart 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. import 'dart:async';
  2. import 'dart:typed_data';
  3. import 'package:flutter/material.dart';
  4. import 'package:photo/src/delegate/badge_delegate.dart';
  5. import 'package:photo/src/delegate/loading_delegate.dart';
  6. import 'package:photo/src/engine/lru_cache.dart';
  7. import 'package:photo/src/engine/throttle.dart';
  8. import 'package:photo/src/entity/options.dart';
  9. import 'package:photo/src/provider/config_provider.dart';
  10. import 'package:photo/src/provider/gallery_list_provider.dart';
  11. import 'package:photo/src/provider/i18n_provider.dart';
  12. import 'package:photo/src/provider/selected_provider.dart';
  13. import 'package:photo/src/ui/dialog/change_gallery_dialog.dart';
  14. import 'package:photo/src/ui/page/photo_preview_page.dart';
  15. import 'package:photo_manager/photo_manager.dart';
  16. part './main/bottom_widget.dart';
  17. part './main/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>
  32. with SelectedProvider, GalleryListProvider {
  33. Options get options => widget.options;
  34. I18nProvider get i18nProvider => ConfigProvider.of(context).provider;
  35. List<AssetEntity> list = [];
  36. Color get themeColor => options.themeColor;
  37. AssetPathEntity _currentPath = AssetPathEntity.all;
  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) {
  50. return i18nProvider.getAllGalleryText(options);
  51. }
  52. return currentPath.name;
  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. @override
  60. void initState() {
  61. super.initState();
  62. _refreshList();
  63. scaffoldKey = GlobalKey();
  64. scrollController = ScrollController();
  65. _changeThrottle = Throttle(onCall: _onAssetChange);
  66. PhotoManager.addChangeCallback(_changeThrottle.call);
  67. PhotoManager.startChangeNotify();
  68. }
  69. @override
  70. void dispose() {
  71. PhotoManager.removeChangeCallback(_changeThrottle.call);
  72. PhotoManager.stopChangeNotify();
  73. _changeThrottle.dispose();
  74. scaffoldKey = null;
  75. super.dispose();
  76. }
  77. @override
  78. Widget build(BuildContext context) {
  79. var textStyle = TextStyle(
  80. color: options.textColor,
  81. fontSize: 14.0,
  82. );
  83. return Theme(
  84. data: Theme.of(context).copyWith(primaryColor: options.themeColor),
  85. child: DefaultTextStyle(
  86. style: textStyle,
  87. child: Scaffold(
  88. appBar: AppBar(
  89. leading: IconButton(
  90. icon: Icon(Icons.close),
  91. onPressed: _cancel,
  92. ),
  93. title: Text(
  94. i18nProvider.getTitleText(options),
  95. ),
  96. actions: <Widget>[
  97. FlatButton(
  98. splashColor: Colors.transparent,
  99. child: Text(
  100. i18nProvider.getSureText(options, selectedCount),
  101. style: selectedCount == 0
  102. ? textStyle.copyWith(color: options.disableColor)
  103. : textStyle,
  104. ),
  105. onPressed: selectedCount == 0 ? null : sure,
  106. ),
  107. ],
  108. ),
  109. body: _buildBody(),
  110. bottomNavigationBar: _BottomWidget(
  111. key: scaffoldKey,
  112. provider: i18nProvider,
  113. options: options,
  114. galleryName: currentGalleryName,
  115. onGalleryChange: _onGalleryChange,
  116. onTapPreview: selectedList.isEmpty ? null : _onTapPreview,
  117. selectedProvider: this,
  118. galleryListProvider: this,
  119. ),
  120. ),
  121. ),
  122. );
  123. }
  124. void _cancel() {
  125. selectedList.clear();
  126. widget.onClose(selectedList);
  127. }
  128. @override
  129. bool isUpperLimit() {
  130. var result = selectedCount == options.maxSelected;
  131. if (result) _showTip(i18nProvider.getMaxTipText(options));
  132. return result;
  133. }
  134. void sure() {
  135. widget.onClose?.call(selectedList);
  136. }
  137. void _showTip(String msg) {
  138. if (isPushed) {
  139. return;
  140. }
  141. Scaffold.of(scaffoldKey.currentContext).showSnackBar(
  142. SnackBar(
  143. content: Text(
  144. msg,
  145. style: TextStyle(
  146. color: options.textColor,
  147. fontSize: 14.0,
  148. ),
  149. ),
  150. duration: Duration(milliseconds: 1500),
  151. backgroundColor: themeColor.withOpacity(0.7),
  152. ),
  153. );
  154. }
  155. void _refreshList() {
  156. if (!useAlbum) {
  157. _refreshListFromWidget();
  158. return;
  159. }
  160. _refreshListFromGallery();
  161. }
  162. Future<void> _refreshListFromWidget() async {
  163. galleryPathList.clear();
  164. galleryPathList.addAll(widget.photoList);
  165. this.list.clear();
  166. var assetList = await galleryPathList[0].assetList;
  167. _sortAssetList(assetList);
  168. this.list.addAll(assetList);
  169. setState(() {
  170. _isInit = true;
  171. });
  172. }
  173. Future<void> _refreshListFromGallery() async {
  174. List<AssetPathEntity> pathList;
  175. switch (options.pickType) {
  176. case PickType.onlyImage:
  177. pathList = await PhotoManager.getImageAsset();
  178. break;
  179. case PickType.onlyVideo:
  180. pathList = await PhotoManager.getVideoAsset();
  181. break;
  182. default:
  183. pathList = await PhotoManager.getAssetPathList();
  184. }
  185. if (pathList == null) {
  186. return;
  187. }
  188. options.sortDelegate.sort(pathList);
  189. galleryPathList.clear();
  190. galleryPathList.addAll(pathList);
  191. List<AssetEntity> imageList;
  192. if (pathList.isNotEmpty) {
  193. imageList = await pathList[0].assetList;
  194. _sortAssetList(imageList);
  195. _currentPath = pathList[0];
  196. }
  197. for (var path in pathList) {
  198. if (path.isAll) {
  199. path.name = i18nProvider.getAllGalleryText(options);
  200. }
  201. }
  202. this.list.clear();
  203. if (imageList != null) {
  204. this.list.addAll(imageList);
  205. }
  206. setState(() {
  207. _isInit = true;
  208. });
  209. }
  210. void _sortAssetList(List<AssetEntity> assetList) {
  211. options?.sortDelegate?.assetDelegate?.sort(assetList);
  212. }
  213. Widget _buildBody() {
  214. if (!_isInit) {
  215. return _buildLoading();
  216. }
  217. return Container(
  218. color: options.dividerColor,
  219. child: GridView.builder(
  220. controller: scrollController,
  221. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  222. crossAxisCount: options.rowCount,
  223. childAspectRatio: options.itemRadio,
  224. crossAxisSpacing: options.padding,
  225. mainAxisSpacing: options.padding,
  226. ),
  227. itemBuilder: _buildItem,
  228. itemCount: list.length,
  229. ),
  230. );
  231. }
  232. Widget _buildItem(BuildContext context, int index) {
  233. var data = list[index];
  234. return RepaintBoundary(
  235. child: GestureDetector(
  236. onTap: () => _onItemClick(data, index),
  237. child: Stack(
  238. children: <Widget>[
  239. ImageItem(
  240. entity: data,
  241. themeColor: themeColor,
  242. size: options.thumbSize,
  243. loadingDelegate: options.loadingDelegate,
  244. badgeDelegate: options.badgeDelegate,
  245. ),
  246. _buildMask(containsEntity(data)),
  247. _buildSelected(data),
  248. ],
  249. ),
  250. ),
  251. );
  252. }
  253. _buildMask(bool showMask) {
  254. return IgnorePointer(
  255. child: AnimatedContainer(
  256. color: showMask ? Colors.black.withOpacity(0.5) : Colors.transparent,
  257. duration: Duration(milliseconds: 300),
  258. ),
  259. );
  260. }
  261. Widget _buildSelected(AssetEntity entity) {
  262. var currentSelected = containsEntity(entity);
  263. return Positioned(
  264. right: 0.0,
  265. width: 36.0,
  266. height: 36.0,
  267. child: GestureDetector(
  268. onTap: () {
  269. changeCheck(!currentSelected, entity);
  270. },
  271. behavior: HitTestBehavior.translucent,
  272. child: _buildText(entity),
  273. ),
  274. );
  275. }
  276. Widget _buildText(AssetEntity entity) {
  277. var isSelected = containsEntity(entity);
  278. Widget child;
  279. BoxDecoration decoration;
  280. if (isSelected) {
  281. child = Text(
  282. (indexOfSelected(entity) + 1).toString(),
  283. textAlign: TextAlign.center,
  284. style: TextStyle(
  285. fontSize: 12.0,
  286. color: options.textColor,
  287. ),
  288. );
  289. decoration = BoxDecoration(color: themeColor);
  290. } else {
  291. decoration = BoxDecoration(
  292. borderRadius: BorderRadius.circular(1.0),
  293. border: Border.all(
  294. color: themeColor,
  295. ),
  296. );
  297. }
  298. return Padding(
  299. padding: const EdgeInsets.all(8.0),
  300. child: AnimatedContainer(
  301. duration: Duration(milliseconds: 300),
  302. decoration: decoration,
  303. alignment: Alignment.center,
  304. child: child,
  305. ),
  306. );
  307. }
  308. void changeCheck(bool value, AssetEntity entity) {
  309. if (value) {
  310. addSelectEntity(entity);
  311. } else {
  312. removeSelectEntity(entity);
  313. }
  314. setState(() {});
  315. }
  316. void _onGalleryChange(AssetPathEntity assetPathEntity) {
  317. _currentPath = assetPathEntity;
  318. _currentPath.assetList.then((v) {
  319. _sortAssetList(v);
  320. list.clear();
  321. list.addAll(v);
  322. scrollController.jumpTo(0.0);
  323. setState(() {});
  324. });
  325. }
  326. void _onItemClick(AssetEntity data, int index) {
  327. var result = new PhotoPreviewResult();
  328. isPushed = true;
  329. Navigator.of(context).push(
  330. MaterialPageRoute(
  331. builder: (ctx) {
  332. return ConfigProvider(
  333. provider: ConfigProvider.of(context).provider,
  334. options: options,
  335. child: PhotoPreviewPage(
  336. selectedProvider: this,
  337. list: List.of(list),
  338. initIndex: index,
  339. changeProviderOnCheckChange: true,
  340. result: result,
  341. ),
  342. );
  343. },
  344. ),
  345. ).then((v) {
  346. if (handlePreviewResult(v)) {
  347. Navigator.pop(context, v);
  348. return;
  349. }
  350. isPushed = false;
  351. setState(() {});
  352. });
  353. }
  354. void _onTapPreview() async {
  355. var result = new PhotoPreviewResult();
  356. isPushed = true;
  357. var v = await Navigator.of(context).push(
  358. MaterialPageRoute(
  359. builder: (ctx) => ConfigProvider(
  360. provider: ConfigProvider.of(context).provider,
  361. options: options,
  362. child: PhotoPreviewPage(
  363. selectedProvider: this,
  364. list: List.of(selectedList),
  365. changeProviderOnCheckChange: false,
  366. result: result,
  367. ),
  368. ),
  369. ),
  370. );
  371. if (handlePreviewResult(v)) {
  372. // print(v);
  373. Navigator.pop(context, v);
  374. return;
  375. }
  376. isPushed = false;
  377. compareAndRemoveEntities(result.previewSelectedList);
  378. }
  379. bool handlePreviewResult(List<AssetEntity> v) {
  380. if (v == null) {
  381. return false;
  382. }
  383. if (v is List<AssetEntity>) {
  384. return true;
  385. }
  386. return false;
  387. }
  388. Widget _buildLoading() {
  389. return Center(
  390. child: Column(
  391. children: <Widget>[
  392. Container(
  393. width: 40.0,
  394. height: 40.0,
  395. padding: const EdgeInsets.all(5.0),
  396. child: CircularProgressIndicator(
  397. valueColor: AlwaysStoppedAnimation(themeColor),
  398. ),
  399. ),
  400. Padding(
  401. padding: const EdgeInsets.all(8.0),
  402. child: Text(
  403. i18nProvider.loadingText(),
  404. style: const TextStyle(
  405. fontSize: 12.0,
  406. ),
  407. ),
  408. ),
  409. ],
  410. crossAxisAlignment: CrossAxisAlignment.center,
  411. mainAxisAlignment: MainAxisAlignment.center,
  412. ),
  413. );
  414. }
  415. void _onAssetChange() {
  416. if (useAlbum) {
  417. _onPhotoRefresh();
  418. }
  419. }
  420. void _onPhotoRefresh() async {
  421. List<AssetPathEntity> pathList;
  422. switch (options.pickType) {
  423. case PickType.onlyImage:
  424. pathList = await PhotoManager.getImageAsset();
  425. break;
  426. case PickType.onlyVideo:
  427. pathList = await PhotoManager.getVideoAsset();
  428. break;
  429. default:
  430. pathList = await PhotoManager.getAssetPathList();
  431. }
  432. if (pathList == null) {
  433. return;
  434. }
  435. this.galleryPathList.clear();
  436. this.galleryPathList.addAll(pathList);
  437. if (!this.galleryPathList.contains(this.currentPath)) {
  438. // current path is deleted , 当前的相册被删除, 应该提示刷新
  439. if (this.galleryPathList.length > 0) {
  440. _onGalleryChange(this.galleryPathList[0]);
  441. }
  442. return;
  443. }
  444. // Not deleted
  445. _onGalleryChange(this.currentPath);
  446. }
  447. }