Açıklama Yok

photo_main_page.dart 13KB

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