Hibok
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 
 

424 rindas
12 KiB

  1. import 'dart:async';
  2. import 'dart:typed_data';
  3. import 'package:chat/generated/i18n.dart';
  4. import 'package:chat/photo/entity/options.dart';
  5. import 'package:chat/photo/provider/asset_provider.dart';
  6. import 'package:chat/photo/provider/config_provider.dart';
  7. import 'package:chat/photo/provider/selected_provider.dart';
  8. import 'package:chat/photo/ui/page/photo_main_page.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:photo_manager/photo_manager.dart';
  11. class PhotoPreviewPage extends StatefulWidget {
  12. final SelectedProvider selectedProvider;
  13. final List<AssetEntity> list;
  14. final int initIndex;
  15. /// 这个参数是控制在内部点击check后是否实时修改provider状态
  16. final bool changeProviderOnCheckChange;
  17. /// 是否通过预览进来的
  18. final bool isPreview;
  19. /// 这里封装了结果
  20. final PhotoPreviewResult result;
  21. final AssetProvider assetProvider;
  22. const PhotoPreviewPage({
  23. Key key,
  24. @required this.selectedProvider,
  25. @required this.list,
  26. @required this.changeProviderOnCheckChange,
  27. @required this.result,
  28. @required this.assetProvider,
  29. this.initIndex = 0,
  30. this.isPreview = false,
  31. }) : super(key: key);
  32. @override
  33. _PhotoPreviewPageState createState() => _PhotoPreviewPageState();
  34. }
  35. class _PhotoPreviewPageState extends State<PhotoPreviewPage> {
  36. PhotoPickerProvider get config => PhotoPickerProvider.of(context);
  37. AssetProvider get assetProvider => widget.assetProvider;
  38. Options get options => config.options;
  39. Color get themeColor => options.themeColor;
  40. Color get textColor => options.textColor;
  41. SelectedProvider get selectedProvider => widget.selectedProvider;
  42. List<AssetEntity> get list {
  43. if (!widget.isPreview) {
  44. return assetProvider.data;
  45. }
  46. return widget.list;
  47. }
  48. StreamController<int> pageChangeController = StreamController.broadcast();
  49. Stream<int> get pageStream => pageChangeController.stream;
  50. bool get changeProviderOnCheckChange => widget.changeProviderOnCheckChange;
  51. PhotoPreviewResult get result => widget.result;
  52. /// 缩略图用的数据
  53. ///
  54. /// 用于与provider数据联动
  55. List<AssetEntity> get previewList {
  56. return selectedProvider.selectedList;
  57. }
  58. /// 选中的数据
  59. List<AssetEntity> _selectedList = [];
  60. List<AssetEntity> get selectedList {
  61. if (changeProviderOnCheckChange) {
  62. return previewList;
  63. }
  64. return _selectedList;
  65. }
  66. PageController pageController;
  67. @override
  68. void initState() {
  69. super.initState();
  70. pageChangeController.add(0);
  71. pageController = PageController(
  72. initialPage: widget.initIndex,
  73. );
  74. _selectedList.clear();
  75. _selectedList.addAll(selectedProvider.selectedList);
  76. result.previewSelectedList = _selectedList;
  77. }
  78. @override
  79. void dispose() {
  80. pageChangeController.close();
  81. super.dispose();
  82. }
  83. @override
  84. Widget build(BuildContext context) {
  85. int totalCount = 0;
  86. totalCount = assetProvider.current?.assetCount ?? 0;
  87. if (!widget.isPreview) {
  88. totalCount = assetProvider.current.assetCount;
  89. } else {
  90. totalCount = list.length;
  91. }
  92. var data = Theme.of(context);
  93. return Theme(
  94. data: data.copyWith(
  95. primaryColor: options.themeColor,
  96. ),
  97. child: Scaffold(
  98. appBar: AppBar(
  99. backgroundColor: config.options.themeColor,
  100. leading: BackButton(
  101. color: options.textColor,
  102. ),
  103. centerTitle: true,
  104. title: StreamBuilder(
  105. stream: pageStream,
  106. initialData: widget.initIndex,
  107. builder: (ctx, snap) {
  108. return Text(
  109. "${snap.data + 1}/$totalCount",
  110. style: TextStyle(
  111. color: options.textColor,
  112. ),
  113. );
  114. },
  115. ),
  116. actions: <Widget>[
  117. Container(
  118. width: 60,
  119. height: 60,
  120. padding: EdgeInsets.only(right: 20),
  121. alignment: Alignment.center,
  122. child: StreamBuilder<int>(
  123. builder: (ctx, snapshot) {
  124. var index = snapshot.data;
  125. var data = list[index];
  126. var checked = selectedList.contains(data);
  127. return Stack(
  128. alignment: Alignment.center,
  129. children: <Widget>[
  130. IgnorePointer(
  131. child: _buildCheckboxContent(checked, index),
  132. ),
  133. GestureDetector(
  134. onTap: () => _changeSelected(!checked, index),
  135. behavior: HitTestBehavior.translucent,
  136. child: Container(),
  137. ),
  138. ],
  139. );
  140. },
  141. initialData: widget.initIndex,
  142. stream: pageStream,
  143. ))
  144. ],
  145. ),
  146. body: PageView.builder(
  147. controller: pageController,
  148. itemBuilder: _buildItem,
  149. itemCount: totalCount,
  150. onPageChanged: _onPageChanged,
  151. ),
  152. bottomSheet: _buildThumb(),
  153. bottomNavigationBar: _buildBottom(),
  154. ),
  155. );
  156. }
  157. Widget _buildBottom() {
  158. var textStyle = TextStyle(
  159. color: options.textColor,
  160. fontSize: 14.0,
  161. );
  162. return StreamBuilder(
  163. stream: pageStream,
  164. builder: (ctx, s) => Row(
  165. mainAxisSize: MainAxisSize.min,
  166. children: <Widget>[
  167. Expanded(child: SizedBox()),
  168. InkWell(
  169. onTap: selectedList.length == 0 ? null : sure,
  170. child: Container(
  171. padding:
  172. EdgeInsets.symmetric(vertical: 5, horizontal: 15),
  173. margin: EdgeInsets.symmetric(vertical: 5, horizontal: 5),
  174. decoration: BoxDecoration(
  175. color: selectedList.length > 0
  176. ? Color(0xFF2D81FF)
  177. : Color(0xFFC7C7C7),
  178. borderRadius: BorderRadius.circular(8)),
  179. child: Text(
  180. "${I18n.of(context).determine}(${selectedList.length})",
  181. style: textStyle.copyWith(color: Colors.white),
  182. ),
  183. )), Expanded(child: SizedBox()),
  184. ],
  185. ));
  186. }
  187. Widget _buildCheckboxContent(bool checked, int index) {
  188. Widget child;
  189. BoxDecoration decoration;
  190. if (checked) {
  191. child = Text(
  192. (index + 1).toString(),
  193. textAlign: TextAlign.center,
  194. style: TextStyle(
  195. fontSize: 12.0,
  196. color: Colors.white,
  197. ),
  198. );
  199. decoration =
  200. BoxDecoration(color: Color(0xFF2D81FF), shape: BoxShape.circle);
  201. } else {
  202. child = Icon(Icons.check);
  203. decoration = BoxDecoration(
  204. shape: BoxShape.circle,
  205. border: Border.all(
  206. color: Colors.black,
  207. ),
  208. color: Colors.white);
  209. }
  210. return Padding(
  211. padding: const EdgeInsets.all(8.0),
  212. child: AnimatedContainer(
  213. duration: Duration(milliseconds: 300),
  214. decoration: decoration,
  215. alignment: Alignment.center,
  216. child: child,
  217. ),
  218. );
  219. }
  220. void _changeSelected(bool isChecked, int index) {
  221. if (changeProviderOnCheckChange) {
  222. _onChangeProvider(isChecked, index);
  223. } else {
  224. _onCheckInOnlyPreview(isChecked, index);
  225. }
  226. }
  227. /// 仅仅修改预览时的状态,在退出时,再更新provider的顺序,这里无论添加与否不修改顺序
  228. void _onCheckInOnlyPreview(bool check, int index) {
  229. var item = list[index];
  230. if (check) {
  231. selectedList.add(item);
  232. } else {
  233. selectedList.remove(item);
  234. }
  235. pageChangeController.add(index);
  236. }
  237. /// 直接修改预览状态,会直接移除item
  238. void _onChangeProvider(bool check, int index) {
  239. var item = list[index];
  240. if (check) {
  241. selectedProvider.addSelectEntity(item);
  242. } else {
  243. selectedProvider.removeSelectEntity(item);
  244. }
  245. pageChangeController.add(index);
  246. }
  247. Widget _buildItem(BuildContext context, int index) {
  248. if (!widget.isPreview && index >= list.length - 5) {
  249. _loadMore();
  250. }
  251. var data = list[index];
  252. return BigPhotoImage(
  253. assetEntity: data,
  254. loadingWidget: _buildLoadingWidget(data),
  255. );
  256. }
  257. Future<void> _loadMore() async {
  258. assetProvider.loadMore();
  259. }
  260. Widget _buildLoadingWidget(AssetEntity entity) {
  261. return options.loadingDelegate
  262. .buildBigImageLoading(context, entity, themeColor);
  263. }
  264. void _onPageChanged(int value) {
  265. pageChangeController.add(value);
  266. }
  267. Widget _buildThumb() {
  268. return Container(
  269. height: 80.0,
  270. color: Color(0xFFF0F0F0),
  271. child: ListView.builder(
  272. padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
  273. itemBuilder: _buildThumbItem,
  274. itemCount: previewList.length,
  275. scrollDirection: Axis.horizontal,
  276. ),
  277. );
  278. }
  279. Widget _buildThumbItem(BuildContext context, int index) {
  280. var item = previewList[index];
  281. return StreamBuilder<int>(
  282. initialData: widget.initIndex,
  283. builder: (ctx, snapshot) => RepaintBoundary(
  284. child: GestureDetector(
  285. onTap: () => changeSelected(item, index),
  286. child: Container(
  287. width: 80.0,
  288. child: Container(
  289. margin: EdgeInsets.all(10),
  290. decoration: BoxDecoration(
  291. border: snapshot.data == index
  292. ? Border.all(color: Colors.blue, width: 2)
  293. : null),
  294. child: Stack(
  295. children: <Widget>[
  296. ImageItem(
  297. themeColor: themeColor,
  298. entity: item,
  299. size: options.thumbSize,
  300. loadingDelegate: options.loadingDelegate,
  301. ),
  302. IgnorePointer(
  303. child: selectedList.contains(item)
  304. ? Container()
  305. : Container(
  306. color: Colors.white.withOpacity(0.5),
  307. )),
  308. ],
  309. ),
  310. ),
  311. )),
  312. ),
  313. stream: pageStream,
  314. );
  315. }
  316. void changeSelected(AssetEntity entity, int index) {
  317. var itemIndex = list.indexOf(entity);
  318. if (itemIndex != -1) pageController.jumpToPage(itemIndex);
  319. }
  320. void sure() {
  321. Navigator.pop(context, selectedList);
  322. }
  323. }
  324. class BigPhotoImage extends StatefulWidget {
  325. final AssetEntity assetEntity;
  326. final Widget loadingWidget;
  327. const BigPhotoImage({
  328. Key key,
  329. this.assetEntity,
  330. this.loadingWidget,
  331. }) : super(key: key);
  332. @override
  333. _BigPhotoImageState createState() => _BigPhotoImageState();
  334. }
  335. class _BigPhotoImageState extends State<BigPhotoImage>
  336. with AutomaticKeepAliveClientMixin {
  337. Widget get loadingWidget {
  338. return widget.loadingWidget ?? Container();
  339. }
  340. @override
  341. Widget build(BuildContext context) {
  342. super.build(context);
  343. var width = MediaQuery.of(context).size.width;
  344. var height = MediaQuery.of(context).size.height;
  345. return FutureBuilder(
  346. future:
  347. widget.assetEntity.thumbDataWithSize(width.floor(), height.floor()),
  348. builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
  349. var file = snapshot.data;
  350. if (snapshot.connectionState == ConnectionState.done && file != null) {
  351. print(file.length);
  352. return Image.memory(
  353. file,
  354. fit: BoxFit.contain,
  355. width: double.infinity,
  356. height: double.infinity,
  357. );
  358. }
  359. return loadingWidget;
  360. },
  361. );
  362. }
  363. @override
  364. bool get wantKeepAlive => true;
  365. }
  366. class PhotoPreviewResult {
  367. List<AssetEntity> previewSelectedList = [];
  368. }