zefyr

asset_picker_viewer_builder_delegate.dart 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. part of ct_assets_picker;
  2. class ExtendedAssetPickerViewerBuilderDelegate
  3. extends DefaultAssetPickerViewerBuilderDelegate {
  4. ExtendedAssetPickerViewerBuilderDelegate({
  5. @required int currentIndex,
  6. @required List<AssetEntity> previewAssets,
  7. @required AssetPickerViewerProvider<AssetEntity> provider,
  8. @required ThemeData themeData,
  9. List<AssetEntity> selectedAssets,
  10. AssetPickerProvider<AssetEntity, AssetPathEntity> selectorProvider,
  11. List<int> previewThumbSize,
  12. SpecialPickerType specialPickerType,
  13. }) : super(
  14. currentIndex: currentIndex,
  15. previewAssets: previewAssets,
  16. provider: provider,
  17. themeData: themeData,
  18. selectedAssets: selectedAssets,
  19. selectorProvider: selectorProvider,
  20. previewThumbSize: previewThumbSize,
  21. specialPickerType: specialPickerType,
  22. );
  23. /// Whether the current platform is Apple OS.
  24. /// 当前平台是否为苹果系列系统
  25. @override
  26. bool get isAppleOS => false;
  27. /// [Duration] when triggering path switching.
  28. /// 切换路径时的动画时长
  29. Duration get switchingPathDuration => kThemeAnimationDuration * 1.5;
  30. /// [Curve] when triggering path switching.
  31. /// 切换路径时的动画曲线
  32. Curve get switchingPathCurve => Curves.easeInOut;
  33. /// AppBar widget.
  34. /// 顶栏部件
  35. @override
  36. Widget appBar(BuildContext context) {
  37. return AnimatedPositioned(
  38. duration: kThemeAnimationDuration,
  39. curve: Curves.easeInOut,
  40. top: isDisplayingDetail ? 0.0 : -(Screens.topSafeHeight + kToolbarHeight),
  41. left: 0.0,
  42. right: 0.0,
  43. height: Screens.topSafeHeight + kToolbarHeight,
  44. child: Container(
  45. padding: EdgeInsets.only(top: Screens.topSafeHeight, right: 12.0),
  46. color: themeData.canvasColor.withOpacity(0.85),
  47. child: Row(
  48. children: <Widget>[
  49. const BackButton(),
  50. if (!isAppleOS && specialPickerType == null)
  51. StreamBuilder<int>(
  52. initialData: currentIndex,
  53. stream: pageStreamController.stream,
  54. builder: (BuildContext _, AsyncSnapshot<int> snapshot) {
  55. if (previewAssets.isEmpty) {
  56. return Container();
  57. }
  58. return Text(
  59. '${snapshot.data + 1}/${previewAssets.length}',
  60. style: const TextStyle(
  61. fontSize: 18.0,
  62. fontWeight: FontWeight.bold,
  63. ),
  64. );
  65. },
  66. ),
  67. const Spacer(),
  68. if (isAppleOS && provider != null) selectButton(context),
  69. if (!isAppleOS && provider != null ||
  70. specialPickerType == SpecialPickerType.wechatMoment)
  71. confirmButton(context),
  72. ],
  73. ),
  74. ),
  75. );
  76. }
  77. /// Select button for apple OS.
  78. /// 苹果系列系统的选择按钮
  79. Widget _appleOSSelectButton(bool isSelected, AssetEntity asset) {
  80. return Padding(
  81. padding: const EdgeInsets.only(right: 10.0),
  82. child: GestureDetector(
  83. behavior: HitTestBehavior.opaque,
  84. onTap: () {
  85. if (isSelected) {
  86. provider.unSelectAssetEntity(asset);
  87. } else {
  88. provider.selectAssetEntity(asset);
  89. }
  90. },
  91. child: AnimatedContainer(
  92. duration: kThemeAnimationDuration,
  93. width: 28.0,
  94. decoration: BoxDecoration(
  95. border: !isSelected
  96. ? Border.all(
  97. color: themeData.iconTheme.color,
  98. )
  99. : null,
  100. color: isSelected ? themeData.buttonColor : null,
  101. shape: BoxShape.circle,
  102. ),
  103. child: Center(
  104. child: isSelected
  105. ? Text(
  106. (currentIndex + 1).toString(),
  107. style: const TextStyle(
  108. fontSize: 16.0,
  109. fontWeight: FontWeight.bold,
  110. ),
  111. )
  112. : const Icon(Icons.check, size: 20.0),
  113. ),
  114. ),
  115. ),
  116. );
  117. }
  118. /// Select button for Android.
  119. /// 安卓系统的选择按钮
  120. Widget _androidSelectButton(bool isSelected, AssetEntity asset) {
  121. return RoundedCheckbox(
  122. value: isSelected,
  123. onChanged: (bool value) {
  124. if (isSelected) {
  125. provider.unSelectAssetEntity(asset);
  126. } else {
  127. provider.selectAssetEntity(asset);
  128. }
  129. },
  130. materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
  131. );
  132. }
  133. @override
  134. Widget selectButton(BuildContext context) {
  135. return Row(
  136. children: <Widget>[
  137. StreamBuilder<int>(
  138. initialData: currentIndex,
  139. stream: pageStreamController.stream,
  140. builder: (BuildContext _, AsyncSnapshot<int> snapshot) {
  141. return ChangeNotifierProvider<
  142. AssetPickerViewerProvider<AssetEntity>>.value(
  143. value: provider,
  144. child: Selector<AssetPickerViewerProvider<AssetEntity>,
  145. List<AssetEntity>>(
  146. selector: (
  147. BuildContext _,
  148. AssetPickerViewerProvider<AssetEntity> provider,
  149. ) =>
  150. provider.currentlySelectedAssets,
  151. builder: (
  152. BuildContext _,
  153. List<AssetEntity> currentlySelectedAssets,
  154. Widget __,
  155. ) {
  156. final AssetEntity asset =
  157. previewAssets.elementAt(snapshot.data);
  158. final bool isSelected =
  159. currentlySelectedAssets.contains(asset);
  160. if (isAppleOS) {
  161. return _appleOSSelectButton(isSelected, asset);
  162. } else {
  163. return _androidSelectButton(isSelected, asset);
  164. }
  165. },
  166. ),
  167. );
  168. },
  169. ),
  170. if (!isAppleOS)
  171. Text(
  172. Constants.textDelegate.select,
  173. style: const TextStyle(fontSize: 18.0),
  174. ),
  175. ],
  176. );
  177. }
  178. /// It'll pop with [AssetPickerProvider.selectedAssets] when there're any
  179. /// assets chosen. The [PhotoSelector] will recognize and pop too.
  180. /// 当有资源已选时,点击按钮将把已选资源通过路由返回。
  181. /// 资源选择器将识别并一同返回。
  182. @override
  183. Widget confirmButton(BuildContext context) {
  184. return ChangeNotifierProvider<AssetPickerViewerProvider<AssetEntity>>.value(
  185. value: provider,
  186. child: Consumer<AssetPickerViewerProvider<AssetEntity>>(
  187. builder: (
  188. BuildContext _,
  189. AssetPickerViewerProvider<AssetEntity> provider,
  190. Widget __,
  191. ) {
  192. return FlatButton(
  193. minWidth: () {
  194. if (specialPickerType == SpecialPickerType.wechatMoment) {
  195. return 48.0;
  196. }
  197. return provider.isSelectedNotEmpty ? 48.0 : 20.0;
  198. }(),
  199. height: 32.0,
  200. padding: const EdgeInsets.symmetric(horizontal: 12.0),
  201. color: () {
  202. if (specialPickerType == SpecialPickerType.wechatMoment) {
  203. return themeData.colorScheme.secondary;
  204. }
  205. return provider.isSelectedNotEmpty
  206. ? themeData.colorScheme.primary
  207. : themeData.dividerColor;
  208. }(),
  209. shape: RoundedRectangleBorder(
  210. borderRadius: BorderRadius.circular(3.0),
  211. ),
  212. child: Text(
  213. () {
  214. if (specialPickerType == SpecialPickerType.wechatMoment) {
  215. return Constants.textDelegate.confirm;
  216. }
  217. if (provider.isSelectedNotEmpty) {
  218. return '${Constants.textDelegate.confirm}'
  219. '(${provider.currentlySelectedAssets.length}'
  220. '/'
  221. '${selectorProvider.maxAssets})';
  222. }
  223. return Constants.textDelegate.confirm;
  224. }(),
  225. style: TextStyle(
  226. color: () {
  227. if (specialPickerType == SpecialPickerType.wechatMoment) {
  228. return themeData.textTheme.bodyText1.color;
  229. }
  230. return provider.isSelectedNotEmpty
  231. ? themeData.colorScheme.onPrimary
  232. : themeData.textTheme.caption.color;
  233. }(),
  234. fontSize: 17.0,
  235. fontWeight: FontWeight.normal,
  236. ),
  237. ),
  238. onPressed: () {
  239. if (specialPickerType == SpecialPickerType.wechatMoment) {
  240. Navigator.of(context).pop(<AssetEntity>[currentAsset]);
  241. return;
  242. }
  243. if (provider.isSelectedNotEmpty) {
  244. Navigator.of(context).pop(provider.currentlySelectedAssets);
  245. }
  246. },
  247. materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
  248. );
  249. },
  250. ),
  251. );
  252. }
  253. /// Preview item widgets for audios.
  254. /// 音频的底部预览部件
  255. Widget _audioPreviewItem(AssetEntity asset) {
  256. return ColoredBox(
  257. color: themeData?.dividerColor,
  258. child: const Center(child: Icon(Icons.audiotrack)),
  259. );
  260. }
  261. /// Preview item widgets for images.
  262. /// 音频的底部预览部件
  263. Widget _imagePreviewItem(AssetEntity asset) {
  264. return Positioned.fill(
  265. child: RepaintBoundary(
  266. child: ExtendedImage(
  267. image: AssetEntityImageProvider(
  268. asset,
  269. isOriginal: false,
  270. ),
  271. fit: BoxFit.cover,
  272. ),
  273. ),
  274. );
  275. }
  276. /// Preview item widgets for video.
  277. /// 音频的底部预览部件
  278. Widget _videoPreviewItem(AssetEntity asset) {
  279. return Positioned.fill(
  280. child: Stack(
  281. children: <Widget>[
  282. _imagePreviewItem(asset),
  283. Center(
  284. child: Icon(
  285. Icons.video_library,
  286. color: themeData.colorScheme.surface.withOpacity(0.54),
  287. ),
  288. ),
  289. ],
  290. ),
  291. );
  292. }
  293. Widget fullImageButton(BuildContext context) {
  294. return Selector<DefaultAssetPickerProvider, bool>(
  295. selector: (BuildContext _, DefaultAssetPickerProvider provider) =>
  296. (provider as ExtendedAssetPickerProvider).fullImage,
  297. builder: (BuildContext _, bool fullImage, Widget __) {
  298. return GestureDetector(
  299. onTap: () {
  300. (selectorProvider as ExtendedAssetPickerProvider).fullImage = !fullImage;
  301. },
  302. child: Row(
  303. mainAxisSize: MainAxisSize.min,
  304. mainAxisAlignment: MainAxisAlignment.center,
  305. children: [
  306. Container(
  307. // margin: EdgeInsets.all(Screens.width / gridCount / 15.0),
  308. width: 18,
  309. height: 18,
  310. alignment: Alignment.center,
  311. child: AnimatedContainer(
  312. duration: switchingPathDuration,
  313. width: 18,
  314. height: 18,
  315. decoration: BoxDecoration(
  316. border: Border.all(
  317. color: fullImage
  318. ? themeData.colorScheme.primary
  319. : themeData.unselectedWidgetColor,
  320. width: 2.0),
  321. color: null,
  322. shape: BoxShape.circle,
  323. ),
  324. child: AnimatedSwitcher(
  325. duration: switchingPathDuration,
  326. reverseDuration: switchingPathDuration,
  327. child: fullImage
  328. ? Container(
  329. width: 10,
  330. height: 10,
  331. decoration: BoxDecoration(
  332. color: themeData.colorScheme.primary,
  333. shape: BoxShape.circle,
  334. ),
  335. )
  336. : const SizedBox.shrink(),
  337. ),
  338. ),
  339. ),
  340. SizedBox(
  341. width: 8,
  342. ),
  343. Text(
  344. Constants.textDelegate.original,
  345. style: TextStyle(
  346. color: themeData.textTheme.caption.color,
  347. fontSize: 18.0,
  348. ),
  349. ),
  350. ],
  351. ),
  352. );
  353. },
  354. );
  355. }
  356. @override
  357. Widget bottomDetailBuilder(BuildContext context) {
  358. return AnimatedPositioned(
  359. duration: kThemeAnimationDuration,
  360. curve: Curves.easeInOut,
  361. bottom: isDisplayingDetail
  362. ? 0.0
  363. : -(Screens.bottomSafeHeight + bottomDetailHeight),
  364. left: 0.0,
  365. right: 0.0,
  366. height: Screens.bottomSafeHeight + bottomDetailHeight,
  367. child: Container(
  368. padding: EdgeInsets.only(bottom: Screens.bottomSafeHeight),
  369. color: themeData.canvasColor.withOpacity(0.85),
  370. child: Column(
  371. children: <Widget>[
  372. ChangeNotifierProvider<
  373. AssetPickerViewerProvider<AssetEntity>>.value(
  374. value: provider,
  375. child: SizedBox(
  376. height: 90.0,
  377. child: Selector<AssetPickerViewerProvider<AssetEntity>,
  378. List<AssetEntity>>(
  379. selector: (
  380. BuildContext _,
  381. AssetPickerViewerProvider<AssetEntity> provider,
  382. ) =>
  383. provider.currentlySelectedAssets,
  384. builder: (
  385. BuildContext _,
  386. List<AssetEntity> currentlySelectedAssets,
  387. Widget __,
  388. ) {
  389. return ListView.builder(
  390. scrollDirection: Axis.horizontal,
  391. padding: const EdgeInsets.symmetric(horizontal: 5.0),
  392. itemCount: currentlySelectedAssets.length,
  393. itemBuilder: bottomDetailItemBuilder,
  394. );
  395. },
  396. ),
  397. ),
  398. ),
  399. Container(
  400. height: 1.0,
  401. color: themeData.dividerColor,
  402. ),
  403. Expanded(
  404. child: Padding(
  405. padding: const EdgeInsets.symmetric(horizontal: 20.0),
  406. child: Row(
  407. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  408. children: <Widget>[
  409. // const Spacer(),
  410. fullImageButton(context),
  411. if (isAppleOS && provider != null)
  412. ChangeNotifierProvider<
  413. AssetPickerViewerProvider<AssetEntity>>.value(
  414. value: provider,
  415. child: confirmButton(context),
  416. )
  417. else
  418. selectButton(context),
  419. ],
  420. ),
  421. ),
  422. ),
  423. ],
  424. ),
  425. ),
  426. );
  427. }
  428. @override
  429. Widget bottomDetailItemBuilder(BuildContext context, int index) {
  430. return Padding(
  431. padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
  432. child: AspectRatio(
  433. aspectRatio: 1.0,
  434. child: StreamBuilder<int>(
  435. initialData: currentIndex,
  436. stream: pageStreamController.stream,
  437. builder: (BuildContext _, AsyncSnapshot<int> snapshot) {
  438. final AssetEntity asset = provider.currentlySelectedAssets.elementAt(index);
  439. final bool isViewing = asset == currentAsset;
  440. return GestureDetector(
  441. onTap: () {
  442. if (!isViewing) {
  443. pageController.jumpToPage(previewAssets.indexWhere((e) => e.id == asset.id));
  444. }
  445. },
  446. child: Selector<AssetPickerViewerProvider<AssetEntity>,
  447. List<AssetEntity>>(
  448. selector: (
  449. BuildContext _,
  450. AssetPickerViewerProvider<AssetEntity> provider,
  451. ) =>
  452. provider.currentlySelectedAssets,
  453. builder: (
  454. BuildContext _,
  455. List<AssetEntity> currentlySelectedAssets,
  456. Widget __,
  457. ) {
  458. final bool isSelected =
  459. currentlySelectedAssets.contains(asset);
  460. return Stack(
  461. children: <Widget>[
  462. () {
  463. Widget item;
  464. switch (asset.type) {
  465. case AssetType.other:
  466. item = const SizedBox.shrink();
  467. break;
  468. case AssetType.image:
  469. item = _imagePreviewItem(asset);
  470. break;
  471. case AssetType.video:
  472. item = _videoPreviewItem(asset);
  473. break;
  474. case AssetType.audio:
  475. item = _audioPreviewItem(asset);
  476. break;
  477. }
  478. return item;
  479. }(),
  480. AnimatedContainer(
  481. duration: kThemeAnimationDuration,
  482. curve: Curves.easeInOut,
  483. decoration: BoxDecoration(
  484. border: isViewing
  485. ? Border.all(
  486. color: themeData.colorScheme.primary,
  487. width: 2.0,
  488. )
  489. : null,
  490. color: isSelected
  491. ? null
  492. : themeData.colorScheme.surface.withOpacity(0.54),
  493. ),
  494. ),
  495. ],
  496. );
  497. },
  498. ),
  499. );
  500. },
  501. ),
  502. ),
  503. );
  504. }
  505. }