Sin descripción

photo_main_page.dart 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. import 'dart:typed_data';
  2. import 'package:flutter/material.dart';
  3. import 'package:photo/src/engine/lru_cache.dart';
  4. import 'package:photo/src/entity/options.dart';
  5. import 'package:photo/src/provider/config_provider.dart';
  6. import 'package:photo/src/provider/gallery_list_provider.dart';
  7. import 'package:photo/src/provider/i18n_provider.dart';
  8. import 'package:photo/src/provider/selected_provider.dart';
  9. import 'package:photo/src/ui/dialog/change_gallery_dialog.dart';
  10. import 'package:photo/src/ui/page/photo_preview_page.dart';
  11. import 'package:photo_manager/photo_manager.dart';
  12. class PhotoMainPage extends StatefulWidget {
  13. final ValueChanged<List<ImageEntity>> onClose;
  14. const PhotoMainPage({
  15. Key key,
  16. this.onClose,
  17. }) : super(key: key);
  18. @override
  19. _PhotoMainPageState createState() => _PhotoMainPageState();
  20. }
  21. class _PhotoMainPageState extends State<PhotoMainPage>
  22. with SelectedProvider, GalleryListProvider {
  23. Options get options => ConfigProvider.of(context).options;
  24. I18nProvider get i18nProvider => ConfigProvider.of(context).provider;
  25. List<ImageEntity> list = [];
  26. Color get themeColor => options.themeColor;
  27. ImagePathEntity _currentPath = ImagePathEntity.all;
  28. ImagePathEntity get currentPath {
  29. if (_currentPath == null) {
  30. return null;
  31. }
  32. return _currentPath;
  33. }
  34. set currentPath(ImagePathEntity value) {
  35. _currentPath = value;
  36. }
  37. GlobalKey scaffoldKey;
  38. ScrollController scrollController;
  39. bool isPushed = false;
  40. @override
  41. void initState() {
  42. super.initState();
  43. _refreshList();
  44. scaffoldKey = GlobalKey();
  45. scrollController = ScrollController();
  46. }
  47. @override
  48. void dispose() {
  49. scaffoldKey = null;
  50. super.dispose();
  51. }
  52. @override
  53. Widget build(BuildContext context) {
  54. var textStyle = TextStyle(
  55. color: options.textColor,
  56. fontSize: 14.0,
  57. );
  58. return Theme(
  59. data: Theme.of(context).copyWith(primaryColor: options.themeColor),
  60. child: DefaultTextStyle(
  61. style: textStyle,
  62. child: Scaffold(
  63. appBar: AppBar(
  64. leading: IconButton(
  65. icon: Icon(Icons.close),
  66. onPressed: _cancel,
  67. ),
  68. title: Text(
  69. i18nProvider.getTitleText(options),
  70. ),
  71. actions: <Widget>[
  72. FlatButton(
  73. child: Text(
  74. i18nProvider.getSureText(options, selectedCount),
  75. style: selectedCount == 0
  76. ? textStyle.copyWith(color: options.disableColor)
  77. : textStyle,
  78. ),
  79. onPressed: selectedCount == 0 ? null : sure,
  80. ),
  81. ],
  82. ),
  83. body: _buildBody(),
  84. bottomNavigationBar: _BottomWidget(
  85. key: scaffoldKey,
  86. provider: i18nProvider,
  87. options: options,
  88. galleryName: currentPath.name,
  89. onGalleryChange: _onGalleryChange,
  90. onTapPreview: selectedList.isEmpty ? null : _onTapPreview,
  91. selectedProvider: this,
  92. galleryListProvider: this,
  93. ),
  94. ),
  95. ),
  96. );
  97. }
  98. void _cancel() {
  99. selectedList.clear();
  100. widget.onClose(selectedList);
  101. }
  102. @override
  103. bool isUpperLimit() {
  104. var result = selectedCount == options.maxSelected;
  105. if (result) _showTip(i18nProvider.getMaxTipText(options));
  106. return result;
  107. }
  108. void sure() {
  109. widget.onClose?.call(selectedList);
  110. }
  111. void _showTip(String msg) {
  112. if (isPushed) {
  113. return;
  114. }
  115. Scaffold.of(scaffoldKey.currentContext).showSnackBar(
  116. SnackBar(
  117. content: Text(
  118. msg,
  119. style: TextStyle(
  120. color: options.textColor,
  121. fontSize: 14.0,
  122. ),
  123. ),
  124. duration: Duration(milliseconds: 1500),
  125. backgroundColor: themeColor.withOpacity(0.7),
  126. ),
  127. );
  128. }
  129. void _refreshList() async {
  130. var pathList = await ImageScanner.getImagePathList();
  131. galleryPathList.clear();
  132. galleryPathList.addAll(pathList);
  133. var imageList = await currentPath.imageList;
  134. this.list.clear();
  135. this.list.addAll(imageList);
  136. setState(() {});
  137. }
  138. Widget _buildBody() {
  139. return Container(
  140. color: options.disableColor,
  141. child: GridView.builder(
  142. controller: scrollController,
  143. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  144. crossAxisCount: options.rowCount,
  145. childAspectRatio: options.itemRadio,
  146. crossAxisSpacing: options.padding,
  147. mainAxisSpacing: options.padding,
  148. ),
  149. itemBuilder: _buildItem,
  150. itemCount: list.length,
  151. ),
  152. );
  153. }
  154. Widget _buildItem(BuildContext context, int index) {
  155. var data = list[index];
  156. return GestureDetector(
  157. onTap: () => _onItemClick(data, index),
  158. child: Stack(
  159. children: <Widget>[
  160. ImageItem(
  161. entity: data,
  162. themeColor: themeColor,
  163. ),
  164. _buildMask(containsEntity(data)),
  165. _buildSelected(data),
  166. ],
  167. ),
  168. );
  169. }
  170. _buildMask(bool showMask) {
  171. return IgnorePointer(
  172. child: AnimatedContainer(
  173. color: showMask ? Colors.black.withOpacity(0.5) : Colors.transparent,
  174. duration: Duration(milliseconds: 300),
  175. ),
  176. );
  177. }
  178. Widget _buildSelected(ImageEntity entity) {
  179. var currentSelected = containsEntity(entity);
  180. return Positioned(
  181. right: 0.0,
  182. width: 36.0,
  183. height: 36.0,
  184. child: GestureDetector(
  185. onTap: () {
  186. changeCheck(!currentSelected, entity);
  187. },
  188. behavior: HitTestBehavior.translucent,
  189. child: _buildText(entity),
  190. ),
  191. );
  192. }
  193. Widget _buildText(ImageEntity entity) {
  194. var isSelected = containsEntity(entity);
  195. Widget child;
  196. BoxDecoration decoration;
  197. if (isSelected) {
  198. child = Text(
  199. (indexOfSelected(entity) + 1).toString(),
  200. textAlign: TextAlign.center,
  201. style: TextStyle(
  202. fontSize: 12.0,
  203. color: options.textColor,
  204. ),
  205. );
  206. decoration = BoxDecoration(color: themeColor);
  207. } else {
  208. decoration = BoxDecoration(
  209. borderRadius: BorderRadius.circular(1.0),
  210. border: Border.all(
  211. color: themeColor,
  212. ),
  213. );
  214. }
  215. return Padding(
  216. padding: const EdgeInsets.all(8.0),
  217. child: AnimatedContainer(
  218. duration: Duration(milliseconds: 300),
  219. decoration: decoration,
  220. alignment: Alignment.center,
  221. child: child,
  222. ),
  223. );
  224. }
  225. void changeCheck(bool value, ImageEntity entity) {
  226. if (value) {
  227. addSelectEntity(entity);
  228. } else {
  229. removeSelectEntity(entity);
  230. }
  231. setState(() {});
  232. }
  233. void _onGalleryChange(ImagePathEntity value) {
  234. _currentPath = value;
  235. _currentPath.imageList.then((v) {
  236. list.clear();
  237. list.addAll(v);
  238. scrollController.jumpTo(0.0);
  239. setState(() {});
  240. });
  241. }
  242. void _onItemClick(ImageEntity data, int index) {
  243. var result = new PhotoPreviewResult();
  244. isPushed = true;
  245. Navigator.of(context).push(
  246. MaterialPageRoute(
  247. builder: (ctx) {
  248. return ConfigProvider(
  249. provider: ConfigProvider.of(context).provider,
  250. options: options,
  251. child: PhotoPreviewPage(
  252. selectedProvider: this,
  253. list: List.of(list),
  254. initIndex: index,
  255. changeProviderOnCheckChange: true,
  256. result: result,
  257. ),
  258. );
  259. },
  260. ),
  261. ).then((v) {
  262. if (handlePreviewResult(v)) {
  263. Navigator.pop(context, v);
  264. return;
  265. }
  266. isPushed = false;
  267. setState(() {});
  268. });
  269. }
  270. void _onTapPreview() {
  271. var result = new PhotoPreviewResult();
  272. isPushed = true;
  273. Navigator.of(context)
  274. .push(
  275. MaterialPageRoute(
  276. builder: (ctx) => ConfigProvider(
  277. provider: ConfigProvider.of(context).provider,
  278. options: options,
  279. child: PhotoPreviewPage(
  280. selectedProvider: this,
  281. list: List.of(selectedList),
  282. changeProviderOnCheckChange: false,
  283. result: result,
  284. ),
  285. ),
  286. ),
  287. )
  288. .then(
  289. (v) {
  290. if (handlePreviewResult(v)) {
  291. print(v);
  292. Navigator.pop(context, v);
  293. return;
  294. }
  295. isPushed = false;
  296. compareAndRemoveEntities(result.previewSelectedList);
  297. },
  298. );
  299. }
  300. bool handlePreviewResult(List<ImageEntity> v) {
  301. if (v == null) {
  302. return false;
  303. }
  304. if (v is List<ImageEntity>) {
  305. return true;
  306. }
  307. return false;
  308. }
  309. }
  310. class _BottomWidget extends StatefulWidget {
  311. final ValueChanged<ImagePathEntity> onGalleryChange;
  312. final Options options;
  313. final I18nProvider provider;
  314. final SelectedProvider selectedProvider;
  315. final String galleryName;
  316. final GalleryListProvider galleryListProvider;
  317. final VoidCallback onTapPreview;
  318. const _BottomWidget({
  319. Key key,
  320. this.onGalleryChange,
  321. this.options,
  322. this.provider,
  323. this.selectedProvider,
  324. this.galleryName = "",
  325. this.galleryListProvider,
  326. this.onTapPreview,
  327. }) : super(key: key);
  328. @override
  329. __BottomWidgetState createState() => __BottomWidgetState();
  330. }
  331. class __BottomWidgetState extends State<_BottomWidget> {
  332. Options get options => widget.options;
  333. I18nProvider get i18nProvider => widget.provider;
  334. @override
  335. Widget build(BuildContext context) {
  336. var textStyle = TextStyle(fontSize: 14.0);
  337. const textPadding = const EdgeInsets.symmetric(horizontal: 16.0);
  338. return Container(
  339. color: options.themeColor,
  340. child: SafeArea(
  341. bottom: true,
  342. top: false,
  343. child: Container(
  344. height: 52.0,
  345. child: Row(
  346. children: <Widget>[
  347. GestureDetector(
  348. behavior: HitTestBehavior.translucent,
  349. onTap: _showGallerySelectDialog,
  350. child: Container(
  351. alignment: Alignment.center,
  352. height: 44.0,
  353. padding: textPadding,
  354. child: Text(
  355. widget.galleryName,
  356. style: textStyle.copyWith(color: options.textColor),
  357. ),
  358. ),
  359. ),
  360. Expanded(
  361. child: Container(),
  362. ),
  363. FlatButton(
  364. onPressed: widget.onTapPreview,
  365. textColor: options.textColor,
  366. disabledTextColor: options.disableColor,
  367. child: Container(
  368. height: 44.0,
  369. alignment: Alignment.center,
  370. child: Text(
  371. i18nProvider.getPreviewText(
  372. options, widget.selectedProvider),
  373. style: textStyle,
  374. ),
  375. padding: textPadding,
  376. ),
  377. ),
  378. ],
  379. ),
  380. ),
  381. ),
  382. );
  383. }
  384. void _showGallerySelectDialog() async {
  385. var result = await showModalBottomSheet(
  386. context: context,
  387. builder: (ctx) => ChangeGalleryDialog(
  388. galleryList: widget.galleryListProvider.galleryPathList,
  389. ),
  390. );
  391. if (result != null) widget.onGalleryChange?.call(result);
  392. }
  393. }
  394. class ImageItem extends StatelessWidget {
  395. final ImageEntity entity;
  396. final Color themeColor;
  397. const ImageItem({
  398. Key key,
  399. this.entity,
  400. this.themeColor,
  401. }) : super(key: key);
  402. @override
  403. Widget build(BuildContext context) {
  404. var thumb = ImageLruCache.getData(entity);
  405. if (thumb != null) {
  406. return _buildImageItem(thumb);
  407. }
  408. return FutureBuilder<Uint8List>(
  409. future: entity.thumbData,
  410. builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
  411. var futureData = snapshot.data;
  412. if (snapshot.connectionState == ConnectionState.done &&
  413. futureData != null) {
  414. ImageLruCache.setData(entity, futureData);
  415. return _buildImageItem(futureData);
  416. }
  417. return Center(
  418. child: Container(
  419. width: 20.0,
  420. height: 20.0,
  421. child: CircularProgressIndicator(
  422. valueColor: AlwaysStoppedAnimation(themeColor),
  423. ),
  424. ),
  425. );
  426. },
  427. );
  428. }
  429. Widget _buildImageItem(Uint8List data) {
  430. return Image.memory(
  431. data,
  432. width: double.infinity,
  433. height: double.infinity,
  434. fit: BoxFit.cover,
  435. );
  436. }
  437. }