zefyr

asset_picker_builder_delegate.dart 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. part of ct_assets_picker;
  2. class ExtendedAssetPickerBuilderDelegate extends DefaultAssetPickerBuilderDelegate {
  3. ExtendedAssetPickerBuilderDelegate({
  4. @required ExtendedAssetPickerProvider provider,
  5. int gridCount = 4,
  6. Color themeColor,
  7. AssetsPickerTextDelegate textDelegate,
  8. ThemeData pickerTheme,
  9. SpecialItemPosition specialItemPosition = SpecialItemPosition.none,
  10. WidgetBuilder specialItemBuilder,
  11. bool allowSpecialItemWhenEmpty = false,
  12. List<int> previewThumbSize,
  13. SpecialPickerType specialPickerType,
  14. }) : assert(
  15. provider != null,
  16. 'AssetPickerProvider must be provided and not null.',
  17. ),
  18. assert(
  19. pickerTheme == null || themeColor == null,
  20. 'Theme and theme color cannot be set at the same time.',
  21. ),
  22. super(
  23. provider: provider,
  24. gridCount: gridCount,
  25. themeColor: themeColor,
  26. textDelegate: textDelegate,
  27. pickerTheme: pickerTheme,
  28. specialItemPosition: specialItemPosition,
  29. specialItemBuilder: specialItemBuilder,
  30. allowSpecialItemWhenEmpty: allowSpecialItemWhenEmpty,
  31. previewThumbSize: previewThumbSize,
  32. specialPickerType: specialPickerType,
  33. );
  34. @override
  35. bool get isAppleOS => true;
  36. @override
  37. double get appleOSBlurRadius => 0;
  38. /// Action bar widget aligned to bottom.
  39. /// 底部操作栏部件
  40. @override
  41. Widget bottomActionBar(BuildContext context) {
  42. return !isSingleAssetMode ? Container(
  43. width: Screens.width,
  44. height: bottomActionBarHeight + Screens.bottomSafeHeight,
  45. padding: EdgeInsets.only(
  46. left: 20.0,
  47. right: 20.0,
  48. bottom: Screens.bottomSafeHeight,
  49. ),
  50. color: theme.colorScheme.surface,
  51. child: Row(children: <Widget>[
  52. previewButton(context),
  53. Expanded(
  54. child: fullImageButton(context),
  55. ),
  56. confirmButton(context),
  57. ]),
  58. ) : Container(
  59. width: Screens.width,
  60. height: Screens.bottomSafeHeight,
  61. );
  62. }
  63. Widget fullImageButton(BuildContext context) {
  64. return Selector<DefaultAssetPickerProvider, bool>(
  65. selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
  66. (provider as ExtendedAssetPickerProvider).fullImage,
  67. builder: (BuildContext _, bool fullImage, Widget __) {
  68. return GestureDetector(
  69. onTap: () {
  70. (provider as ExtendedAssetPickerProvider).fullImage = !fullImage;
  71. },
  72. child: Row(
  73. mainAxisSize: MainAxisSize.min,
  74. mainAxisAlignment: MainAxisAlignment.center,
  75. children: [
  76. Container(
  77. // margin: EdgeInsets.all(Screens.width / gridCount / 15.0),
  78. width: 18,
  79. height: 18,
  80. alignment: Alignment.center,
  81. child: AnimatedContainer(
  82. duration: switchingPathDuration,
  83. width: 18,
  84. height: 18,
  85. decoration: BoxDecoration(
  86. border: Border.all(
  87. color: fullImage
  88. ? theme.colorScheme.primary
  89. : theme.unselectedWidgetColor,
  90. width: 2.0),
  91. color: null,
  92. shape: BoxShape.circle,
  93. ),
  94. child: AnimatedSwitcher(
  95. duration: switchingPathDuration,
  96. reverseDuration: switchingPathDuration,
  97. child: fullImage
  98. ? Container(
  99. width: 10,
  100. height: 10,
  101. decoration: BoxDecoration(
  102. color: theme.colorScheme.primary,
  103. shape: BoxShape.circle,
  104. ),
  105. )
  106. : const SizedBox.shrink(),
  107. ),
  108. ),
  109. ),
  110. SizedBox(
  111. width: 8,
  112. ),
  113. Text(
  114. Constants.textDelegate.original,
  115. style: TextStyle(
  116. color: theme.textTheme.caption.color,
  117. fontSize: 18.0,
  118. ),
  119. ),
  120. ],
  121. ),
  122. );
  123. },
  124. );
  125. }
  126. /// The main grid view builder for assets.
  127. /// 主要的资源查看网格部件
  128. @override
  129. Widget assetsGridBuilder(BuildContext context) {
  130. return ColoredBox(
  131. color: theme.colorScheme.background,
  132. child: Selector<AssetPickerProvider<AssetEntity, AssetPathEntity>,
  133. List<AssetEntity>>(
  134. selector: (BuildContext _,
  135. AssetPickerProvider<AssetEntity, AssetPathEntity> provider) =>
  136. provider.currentAssets,
  137. builder: (
  138. BuildContext _,
  139. List<AssetEntity> currentAssets,
  140. Widget __,
  141. ) {
  142. return GridView.builder(
  143. padding: isAppleOS
  144. ? EdgeInsets.only(
  145. top: Screens.topSafeHeight + kToolbarHeight,
  146. bottom: bottomActionBarHeight,
  147. )
  148. : EdgeInsets.zero,
  149. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  150. crossAxisCount: gridCount,
  151. mainAxisSpacing: itemSpacing,
  152. crossAxisSpacing: itemSpacing,
  153. ),
  154. itemCount: assetsGridItemCount(_, currentAssets),
  155. itemBuilder: (BuildContext _, int index) {
  156. return assetGridItemBuilder(_, index, currentAssets);
  157. },
  158. );
  159. },
  160. ),
  161. );
  162. }
  163. /// It'll pop with [AssetPickerProvider.selectedAssets]
  164. /// when there're any assets chosen.
  165. /// 当有资源已选时,点击按钮将把已选资源通过路由返回。
  166. @override
  167. Widget confirmButton(BuildContext context) {
  168. return Consumer<DefaultAssetPickerProvider>(
  169. builder: (
  170. BuildContext _,
  171. DefaultAssetPickerProvider provider,
  172. Widget __,
  173. ) {
  174. return FlatButton(
  175. minWidth: provider.isSelectedNotEmpty ? 48.0 : 20.0,
  176. height: appBarItemHeight,
  177. padding: const EdgeInsets.symmetric(horizontal: 12.0),
  178. color: provider.isSelectedNotEmpty
  179. ? theme.colorScheme.primary
  180. : theme.dividerColor,
  181. shape: RoundedRectangleBorder(
  182. borderRadius: BorderRadius.circular(3.0),
  183. ),
  184. child: Text(
  185. provider.isSelectedNotEmpty && !isSingleAssetMode
  186. ? '${Constants.textDelegate.confirm}'
  187. '(${provider.selectedAssets.length}/${provider.maxAssets})'
  188. : Constants.textDelegate.confirm,
  189. style: TextStyle(
  190. color: provider.isSelectedNotEmpty
  191. ? theme.colorScheme.onPrimary
  192. : theme.textTheme.caption.color,
  193. fontSize: 17.0,
  194. fontWeight: FontWeight.normal,
  195. ),
  196. ),
  197. onPressed: () {
  198. if (provider.isSelectedNotEmpty) {
  199. Navigator.of(context).pop(AssetPickerEntity(
  200. assets: provider.selectedAssets,
  201. isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
  202. ));
  203. }
  204. },
  205. materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
  206. );
  207. },
  208. );
  209. }
  210. @override
  211. Widget previewButton(BuildContext context) {
  212. return Selector<DefaultAssetPickerProvider, bool>(
  213. selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
  214. provider.isSelectedNotEmpty,
  215. builder: (BuildContext _, bool isSelectedNotEmpty, Widget __) {
  216. return GestureDetector(
  217. onTap: isSelectedNotEmpty
  218. ? () async {
  219. final List<AssetEntity> result =
  220. await ExtendedAssetPickerViewer.pushToViewer(
  221. context,
  222. currentIndex: 0,
  223. previewAssets: provider.currentAssets,
  224. previewThumbSize: previewThumbSize,
  225. selectedAssets: provider.selectedAssets,
  226. selectorProvider: provider as ExtendedAssetPickerProvider,
  227. themeData: theme,
  228. );
  229. if (result != null) {
  230. Navigator.of(context).pop(AssetPickerEntity(
  231. assets: result,
  232. isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
  233. ));
  234. }
  235. }
  236. : null,
  237. child: Padding(
  238. padding: const EdgeInsets.symmetric(vertical: 12.0),
  239. child: Selector<DefaultAssetPickerProvider, List<AssetEntity>>(
  240. selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
  241. provider.selectedAssets,
  242. builder: (
  243. BuildContext _,
  244. List<AssetEntity> selectedAssets,
  245. Widget __,
  246. ) {
  247. return Text(
  248. isSelectedNotEmpty
  249. ? '${Constants.textDelegate.preview}'
  250. '(${provider.selectedAssets.length})'
  251. : Constants.textDelegate.preview,
  252. style: TextStyle(
  253. color: isSelectedNotEmpty
  254. ? null
  255. : theme.textTheme.caption.color,
  256. fontSize: 18.0,
  257. ),
  258. );
  259. },
  260. ),
  261. ),
  262. );
  263. },
  264. );
  265. }
  266. @override
  267. Widget selectIndicator(BuildContext context, AssetEntity asset) {
  268. if (isSingleAssetMode) {
  269. return Container();
  270. }
  271. return Selector<DefaultAssetPickerProvider, List<AssetEntity>>(
  272. selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
  273. provider.selectedAssets,
  274. builder: (BuildContext _, List<AssetEntity> selectedAssets, Widget __) {
  275. final bool selected = selectedAssets.contains(asset);
  276. final double indicatorSize = Screens.width / gridCount / 3;
  277. return Positioned(
  278. top: 0.0,
  279. right: 0.0,
  280. child: GestureDetector(
  281. behavior: HitTestBehavior.opaque,
  282. onTap: () {
  283. if (selected) {
  284. provider.unSelectAsset(asset);
  285. } else {
  286. if (isSingleAssetMode) {
  287. provider.selectedAssets.clear();
  288. }
  289. provider.selectAsset(asset);
  290. }
  291. },
  292. child: Container(
  293. margin: EdgeInsets.all(Screens.width / gridCount / 15.0),
  294. width: indicatorSize,
  295. height: indicatorSize,
  296. alignment: AlignmentDirectional.topEnd,
  297. child: AnimatedContainer(
  298. duration: switchingPathDuration,
  299. width: indicatorSize / 1.5,
  300. height: indicatorSize / 1.5,
  301. decoration: BoxDecoration(
  302. border: !selected
  303. ? Border.all(color: Colors.white, width: 2.0)
  304. : null,
  305. color: selected ? theme.colorScheme.primary : null,
  306. shape: BoxShape.circle,
  307. ),
  308. child: AnimatedSwitcher(
  309. duration: switchingPathDuration,
  310. reverseDuration: switchingPathDuration,
  311. child: selected
  312. ? isSingleAssetMode
  313. ? const Icon(Icons.check, size: 18.0)
  314. : Text(
  315. '${selectedAssets.indexOf(asset) + 1}',
  316. style: TextStyle(
  317. color: selected
  318. ? theme.colorScheme.onPrimary
  319. : null,
  320. fontSize: 14.0,
  321. fontWeight: isAppleOS
  322. ? FontWeight.w600
  323. : FontWeight.bold,
  324. ),
  325. )
  326. : const SizedBox.shrink(),
  327. ),
  328. ),
  329. ),
  330. ),
  331. );
  332. },
  333. );
  334. }
  335. @override
  336. Widget selectedBackdrop(BuildContext context, int index, AssetEntity asset) {
  337. return Selector<DefaultAssetPickerProvider, List<AssetEntity>>(
  338. selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
  339. provider.selectedAssets,
  340. builder: (BuildContext _, List<AssetEntity> selectedAssets, Widget __) {
  341. final bool selected = selectedAssets.contains(asset);
  342. return Positioned.fill(
  343. child: GestureDetector(
  344. onTap: () async {
  345. if (isSingleAssetMode) {
  346. Navigator.of(context).pop(AssetPickerEntity(
  347. assets: [asset],
  348. isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
  349. ));
  350. return;
  351. }
  352. final List<AssetEntity> result =
  353. await ExtendedAssetPickerViewer.pushToViewer(
  354. context,
  355. currentIndex: index,
  356. previewAssets: provider.currentAssets,
  357. previewThumbSize: previewThumbSize,
  358. selectedAssets: provider.selectedAssets,
  359. selectorProvider: provider as ExtendedAssetPickerProvider,
  360. themeData: theme,
  361. );
  362. if (result != null) {
  363. Navigator.of(context).pop(AssetPickerEntity(
  364. assets: result,
  365. isFullImage: (provider as ExtendedAssetPickerProvider).fullImage,
  366. ));
  367. }
  368. },
  369. child: AnimatedContainer(
  370. duration: switchingPathDuration,
  371. color: selected
  372. ? Colors.black.withOpacity(0.45)
  373. : Colors.black.withOpacity(0.1),
  374. ),
  375. ), // 点击预览同目录下所有资源
  376. );
  377. },
  378. );
  379. }
  380. @override
  381. Widget pathEntityListWidget(BuildContext context) {
  382. final double appBarHeight = kToolbarHeight + Screens.topSafeHeight;
  383. final double maxHeight = Screens.height * 0.625;
  384. return Selector<DefaultAssetPickerProvider, bool>(
  385. selector: (
  386. BuildContext _,
  387. DefaultAssetPickerProvider provider,
  388. ) =>
  389. provider.isSwitchingPath,
  390. builder: (BuildContext _, bool isSwitchingPath, Widget __) {
  391. final pathHeight = provider.pathEntityList.length * 65.0;
  392. final double height = pathHeight > maxHeight ? maxHeight : pathHeight;
  393. return AnimatedPositioned(
  394. duration: switchingPathDuration,
  395. curve: switchingPathCurve,
  396. top: isAppleOS
  397. ? !isSwitchingPath
  398. ? -height
  399. : appBarHeight
  400. : -(!isSwitchingPath ? maxHeight : 1.0),
  401. child: AnimatedOpacity(
  402. duration: switchingPathDuration,
  403. curve: switchingPathCurve,
  404. opacity: !isAppleOS || isSwitchingPath ? 1.0 : 0.0,
  405. child: Container(
  406. width: Screens.width,
  407. height: height,
  408. decoration: BoxDecoration(
  409. borderRadius: isAppleOS
  410. ? const BorderRadius.only(
  411. bottomLeft: Radius.circular(10.0),
  412. bottomRight: Radius.circular(10.0),
  413. )
  414. : null,
  415. color: theme.colorScheme.surface,
  416. ),
  417. child: Selector<DefaultAssetPickerProvider,
  418. Map<AssetPathEntity, Uint8List>>(
  419. selector: (
  420. BuildContext _,
  421. DefaultAssetPickerProvider provider,
  422. ) =>
  423. provider.pathEntityList,
  424. builder: (
  425. BuildContext _,
  426. Map<AssetPathEntity, Uint8List> pathEntityList,
  427. Widget __,
  428. ) {
  429. return ListView.separated(
  430. padding: const EdgeInsets.only(top: 1.0),
  431. itemCount: pathEntityList.length,
  432. itemBuilder: (BuildContext _, int index) {
  433. return pathEntityWidget(
  434. context,
  435. pathEntityList.keys.elementAt(index),
  436. );
  437. },
  438. separatorBuilder: (BuildContext _, int __) => Container(
  439. margin: const EdgeInsets.only(left: 60.0),
  440. height: 1.0,
  441. color: theme.canvasColor,
  442. ),
  443. );
  444. },
  445. ),
  446. ),
  447. ),
  448. );
  449. },
  450. );
  451. }
  452. @override
  453. Widget pathEntityWidget(BuildContext context, AssetPathEntity path) {
  454. Widget builder(
  455. BuildContext context,
  456. Map<AssetPathEntity, Uint8List> pathEntityList,
  457. Widget __,
  458. ) {
  459. if (context.watch<DefaultAssetPickerProvider>().requestType ==
  460. RequestType.audio) {
  461. return ColoredBox(
  462. color: theme.colorScheme.primary.withOpacity(0.12),
  463. child: const Center(child: Icon(Icons.audiotrack)),
  464. );
  465. }
  466. /// The reason that the `thumbData` should be checked at here to see if it
  467. /// is null is that even the image file is not exist, the `File` can still
  468. /// returned as it exist, which will cause the thumb bytes return null.
  469. ///
  470. /// 此处需要检查缩略图为空的原因是:尽管文件可能已经被删除,但通过`File`读取的文件
  471. /// 对象 仍然存在,使得返回的数据为空。
  472. final Uint8List thumbData = pathEntityList[path];
  473. if (thumbData != null) {
  474. return Image.memory(
  475. pathEntityList[path],
  476. fit: BoxFit.cover,
  477. );
  478. } else {
  479. return ColoredBox(
  480. color: theme.colorScheme.primary.withOpacity(0.12),
  481. );
  482. }
  483. }
  484. return Material(
  485. type: MaterialType.transparency,
  486. child: InkWell(
  487. splashFactory: InkSplash.splashFactory,
  488. onTap: () => provider.switchPath(path),
  489. child: SizedBox(
  490. height: isAppleOS ? 64.0 : 52.0,
  491. child: Padding(
  492. padding: EdgeInsets.symmetric(horizontal: 16),
  493. child: Row(
  494. children: <Widget>[
  495. SizedBox(
  496. width: 50,
  497. height: 50,
  498. child: RepaintBoundary(
  499. child: AspectRatio(
  500. aspectRatio: 1.0,
  501. child: Selector<DefaultAssetPickerProvider,
  502. Map<AssetPathEntity, Uint8List>>(
  503. selector: (
  504. BuildContext _,
  505. DefaultAssetPickerProvider provider,
  506. ) =>
  507. provider.pathEntityList,
  508. builder: builder,
  509. ),
  510. ),
  511. ),
  512. ),
  513. Expanded(
  514. child: Padding(
  515. padding: const EdgeInsets.only(left: 15.0, right: 20.0),
  516. child: Row(
  517. children: <Widget>[
  518. Flexible(
  519. child: Padding(
  520. padding: const EdgeInsets.only(right: 10.0),
  521. child: Text(
  522. path.name ?? '',
  523. style: const TextStyle(fontSize: 16.0),
  524. maxLines: 1,
  525. overflow: TextOverflow.ellipsis,
  526. ),
  527. ),
  528. ),
  529. Text(
  530. '(${path.assetCount})',
  531. style: TextStyle(
  532. color: theme.textTheme.caption.color,
  533. fontSize: 16.0,
  534. ),
  535. maxLines: 1,
  536. overflow: TextOverflow.ellipsis,
  537. ),
  538. ],
  539. ),
  540. ),
  541. ),
  542. Selector<DefaultAssetPickerProvider, AssetPathEntity>(
  543. selector: (
  544. BuildContext _,
  545. DefaultAssetPickerProvider provider,
  546. ) =>
  547. provider.currentPathEntity,
  548. builder: (
  549. BuildContext _,
  550. AssetPathEntity currentPathEntity,
  551. Widget __,
  552. ) {
  553. if (currentPathEntity == path) {
  554. return Icon(ZefyrFont.multiselect_selected,
  555. color: theme.colorScheme.primary, size: 20.0);
  556. } else {
  557. return const SizedBox.shrink();
  558. }
  559. },
  560. ),
  561. ],
  562. ),
  563. ),
  564. ),
  565. ),
  566. );
  567. }
  568. }