privacy_view.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. import 'dart:io';
  2. import 'package:clean/model/asset_info.dart';
  3. import 'package:clean/module/privacy/privacy_controller.dart';
  4. import 'package:clean/module/privacy/privacy_state.dart';
  5. import 'package:clean/router/app_pages.dart';
  6. import 'package:clean/utils/expand.dart';
  7. import 'package:clean/utils/file_utils.dart';
  8. import 'package:clean/utils/image_util.dart';
  9. import 'package:flutter/Material.dart';
  10. import 'package:flutter/cupertino.dart';
  11. import 'package:flutter_screenutil/flutter_screenutil.dart';
  12. import 'package:get/get.dart';
  13. import 'package:wechat_assets_picker/wechat_assets_picker.dart';
  14. import '../../base/base_view.dart';
  15. import '../../dialog/privacy_lock_dialog.dart';
  16. import '../../resource/assets.gen.dart';
  17. import '../more/more_controller.dart';
  18. import 'dart:typed_data';
  19. class PrivacyPage extends BaseView<PrivacyController> {
  20. const PrivacyPage({super.key});
  21. @override
  22. Widget buildBody(BuildContext context) {
  23. return Obx(() {
  24. return Stack(
  25. children: [
  26. !controller.isUnlock.value
  27. ? _buildPasswordPage()
  28. : _buildPrivacySpace(context),
  29. IgnorePointer(
  30. child: Assets.images.bgHome.image(
  31. width: 360.w,
  32. ),
  33. ),
  34. ],
  35. );
  36. });
  37. }
  38. // 输入密码界面
  39. Widget _buildPasswordPage() {
  40. return SafeArea(
  41. child: Container(
  42. padding: EdgeInsets.only(left: 16.w, top: 14.h, right: 16.w),
  43. child: Column(
  44. mainAxisAlignment: MainAxisAlignment.start,
  45. crossAxisAlignment: CrossAxisAlignment.start,
  46. children: [
  47. GestureDetector(
  48. onTap: () {
  49. Get.back();
  50. },
  51. child:
  52. Assets.images.iconCommonBack.image(width: 28.w, height: 28.w),
  53. ),
  54. SizedBox(
  55. height: 28.h,
  56. ),
  57. Align(
  58. child: Column(
  59. children: [
  60. Assets.images.iconPrivacyLock
  61. .image(width: 70.w, height: 70.w),
  62. Text(
  63. controller.passwordTitle.value,
  64. style: TextStyle(
  65. color: Colors.white,
  66. fontSize: 16.sp,
  67. fontWeight: FontWeight.w700,
  68. ),
  69. ),
  70. SizedBox(
  71. height: 28.h,
  72. ),
  73. Obx(() {
  74. return Row(
  75. mainAxisAlignment: MainAxisAlignment.center,
  76. children: [
  77. Container(
  78. width: 32.h,
  79. height: 32.h,
  80. decoration: BoxDecoration(
  81. border: Border.all(
  82. color: "#0279FB".color,
  83. width: 2.w,
  84. ),
  85. borderRadius:
  86. BorderRadius.all(Radius.circular(16.h)),
  87. color: controller.passwordStr.value.isNotEmpty
  88. ? "#0279FB".color
  89. : Colors.transparent),
  90. ),
  91. Container(
  92. margin: EdgeInsets.only(left: 16.w),
  93. width: 32.h,
  94. height: 32.h,
  95. decoration: BoxDecoration(
  96. border: Border.all(
  97. color: "#0279FB".color,
  98. width: 2.w,
  99. ),
  100. borderRadius:
  101. BorderRadius.all(Radius.circular(16.h)),
  102. color: controller.passwordStr.value.length >= 2
  103. ? "#0279FB".color
  104. : Colors.transparent),
  105. ),
  106. Container(
  107. margin: EdgeInsets.only(left: 16.w),
  108. width: 32.h,
  109. height: 32.h,
  110. decoration: BoxDecoration(
  111. border: Border.all(
  112. color: "#0279FB".color,
  113. width: 2.w,
  114. ),
  115. borderRadius:
  116. BorderRadius.all(Radius.circular(16.h)),
  117. color: controller.passwordStr.value.length >= 3
  118. ? "#0279FB".color
  119. : Colors.transparent),
  120. ),
  121. Container(
  122. margin: EdgeInsets.only(left: 16.w),
  123. width: 32.h,
  124. height: 32.h,
  125. decoration: BoxDecoration(
  126. border: Border.all(
  127. color: "#0279FB".color,
  128. width: 2.w,
  129. ),
  130. borderRadius:
  131. BorderRadius.all(Radius.circular(16.h)),
  132. color: controller.passwordStr.value.length >= 4
  133. ? "#0279FB".color
  134. : Colors.transparent),
  135. ),
  136. ],
  137. );
  138. }),
  139. SizedBox(
  140. height: 67.h,
  141. ),
  142. _buildPasswordInput(),
  143. ],
  144. ),
  145. )
  146. ],
  147. ),
  148. ),
  149. );
  150. }
  151. Widget _buildPasswordInput() {
  152. return Column(
  153. children: [
  154. Row(
  155. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  156. children: [
  157. _buildNumberBtn("1"),
  158. _buildNumberBtn("2"),
  159. _buildNumberBtn("3"),
  160. ],
  161. ),
  162. SizedBox(height: 20.h),
  163. Row(
  164. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  165. children: [
  166. _buildNumberBtn("4"),
  167. _buildNumberBtn("5"),
  168. _buildNumberBtn("6"),
  169. ],
  170. ),
  171. SizedBox(height: 20.h),
  172. Row(
  173. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  174. children: [
  175. _buildNumberBtn("7"),
  176. _buildNumberBtn("8"),
  177. _buildNumberBtn("9"),
  178. ],
  179. ),
  180. SizedBox(height: 20.h),
  181. Row(
  182. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  183. children: [
  184. _buildNumberBtn(""),
  185. _buildNumberBtn("0"),
  186. _buildDeleteBtn(),
  187. ],
  188. ),
  189. ],
  190. );
  191. }
  192. Widget _buildNumberBtn(String num) {
  193. return Opacity(
  194. opacity: num.isEmpty ? 0 : 1,
  195. child: GestureDetector(
  196. onTap: () {
  197. controller.inputPassword(num);
  198. },
  199. child: Container(
  200. width: 76.w,
  201. height: 76.w,
  202. decoration: BoxDecoration(
  203. color: "#FFFFFF".color.withOpacity(0.12),
  204. borderRadius: BorderRadius.all(Radius.circular(38.w)),
  205. ),
  206. child: Align(
  207. child: Text(
  208. num,
  209. style: TextStyle(
  210. color: Colors.white,
  211. fontSize: 20.sp,
  212. fontWeight: FontWeight.w700,
  213. ),
  214. ),
  215. ),
  216. ),
  217. ),
  218. );
  219. }
  220. Widget _buildDeleteBtn() {
  221. return GestureDetector(
  222. onTap: () {
  223. if (controller.passwordStr.isNotEmpty) {
  224. controller.passwordStr.value = controller.passwordStr.value
  225. .substring(0, controller.passwordStr.value.length - 1);
  226. }
  227. },
  228. child: Container(
  229. width: 76.w,
  230. height: 76.w,
  231. decoration: BoxDecoration(
  232. color: "#FFFFFF".color.withOpacity(0.12),
  233. borderRadius: BorderRadius.all(Radius.circular(38.w)),
  234. ),
  235. child: Center(
  236. child:
  237. Assets.images.iconPrivacyDelete.image(width: 34.w, height: 24.h),
  238. ),
  239. ),
  240. );
  241. }
  242. Widget _buildPrivacySpace(BuildContext context) {
  243. return SafeArea(
  244. child: Container(
  245. padding: EdgeInsets.only(left: 16.w, top: 14.h, right: 16.w),
  246. child: Obx(() {
  247. return Column(
  248. mainAxisAlignment: MainAxisAlignment.start,
  249. crossAxisAlignment: CrossAxisAlignment.start,
  250. children: [
  251. !controller.isEdit.value
  252. ? Row(
  253. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  254. children: [
  255. GestureDetector(
  256. onTap: () {
  257. Get.back();
  258. },
  259. child: Assets.images.iconCommonBack
  260. .image(width: 28.w, height: 28.w),
  261. ),
  262. Obx(() {
  263. return Visibility(
  264. visible: PrivacyState.imageList.isNotEmpty,
  265. child: GestureDetector(
  266. onTap: () {
  267. controller.isEdit.value = true;
  268. },
  269. child: Container(
  270. width: 71.w,
  271. height: 30.h,
  272. decoration: BoxDecoration(
  273. color: "#1F2D3F".color,
  274. borderRadius: BorderRadius.all(
  275. Radius.circular(15.h),
  276. ),
  277. ),
  278. child: Center(
  279. child: Text(
  280. "Select",
  281. style: TextStyle(
  282. color: Colors.white,
  283. fontSize: 14.sp,
  284. ),
  285. ),
  286. ),
  287. ),
  288. ),
  289. );
  290. }),
  291. ],
  292. )
  293. : Row(
  294. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  295. children: [
  296. GestureDetector(
  297. onTap: () {
  298. controller.isEdit.value = false;
  299. },
  300. child: Container(
  301. width: 71.w,
  302. height: 30.h,
  303. decoration: BoxDecoration(
  304. color: "#1F2D3F".color,
  305. borderRadius: BorderRadius.all(
  306. Radius.circular(15.h),
  307. ),
  308. ),
  309. child: Center(
  310. child: Text(
  311. "Cancel",
  312. style: TextStyle(
  313. color: Colors.white,
  314. fontSize: 14.sp,
  315. ),
  316. ),
  317. ),
  318. ),
  319. ),
  320. Obx(() {
  321. return Visibility(
  322. visible: PrivacyState.imageList.isNotEmpty,
  323. child: GestureDetector(
  324. onTap: () {
  325. controller.toggleSelectAll();
  326. },
  327. child: Text(
  328. "Select All",
  329. style: TextStyle(
  330. color: Colors.white.withOpacity(0.65),
  331. fontSize: 14.sp,
  332. ),
  333. ),
  334. ),
  335. );
  336. }),
  337. ],
  338. ),
  339. SizedBox(
  340. height: 12.h,
  341. ),
  342. Row(
  343. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  344. children: [
  345. Text(
  346. "Privacy Space",
  347. style: TextStyle(
  348. color: Colors.white,
  349. fontWeight: FontWeight.w700,
  350. fontSize: 24.sp,
  351. ),
  352. ),
  353. GestureDetector(
  354. onTap: () {
  355. showDialog(
  356. context: context,
  357. builder: (context) => PrivacyLockDialog(
  358. isPrivacyOn: controller.isPrivacyExistPasswd.value,
  359. onResetPassword: () {
  360. // 处理重置密码
  361. controller.dialogSetPassword();
  362. Navigator.pop(context);
  363. },
  364. onSetPublic: () {
  365. // 处理设为公开
  366. controller.setPublic();
  367. Navigator.pop(context);
  368. },
  369. ),
  370. );
  371. },
  372. child: controller.isPrivacyExistPasswd.value
  373. ? Assets.images.iconPrivacyLock
  374. .image(width: 34.w, height: 34.w)
  375. : Assets.images.iconPrivacyUnlock
  376. .image(width: 34.w, height: 34.w),
  377. ),
  378. ],
  379. ),
  380. PrivacyState.imageList.isEmpty
  381. ? _buildEmptyPhotoView(context)
  382. : _buildPhotoView(),
  383. ],
  384. );
  385. }),
  386. ),
  387. );
  388. }
  389. _buildEmptyPhotoView(BuildContext context) {
  390. return Center(
  391. child: Column(
  392. mainAxisAlignment: MainAxisAlignment.center,
  393. crossAxisAlignment: CrossAxisAlignment.center,
  394. children: [
  395. SizedBox(
  396. height: 130.h,
  397. ),
  398. Assets.images.iconPrivacyEmptyImage.image(width: 70.w, height: 70.w),
  399. SizedBox(
  400. height: 22.h,
  401. ),
  402. Text(
  403. "Upload Files to Privacy Space",
  404. style: TextStyle(
  405. color: Colors.white.withOpacity(0.9),
  406. fontWeight: FontWeight.w500,
  407. fontSize: 18.sp,
  408. ),
  409. ),
  410. SizedBox(
  411. height: 116.h,
  412. ),
  413. GestureDetector(
  414. onTap: () {
  415. controller.uploadBtnClick();
  416. },
  417. child: Container(
  418. width: 180.w,
  419. height: 48.h,
  420. decoration: BoxDecoration(
  421. color: "#0279FB".color,
  422. borderRadius: BorderRadius.all(
  423. Radius.circular(10.r),
  424. ),
  425. ),
  426. child: Center(
  427. child: Text(
  428. "Upload",
  429. style: TextStyle(
  430. color: Colors.white,
  431. fontSize: 16.sp,
  432. fontWeight: FontWeight.w500,
  433. ),
  434. ),
  435. ),
  436. ),
  437. ),
  438. ],
  439. ),
  440. );
  441. }
  442. Widget _buildPhotoView() {
  443. return Expanded(
  444. child: Column(
  445. children: [
  446. SizedBox(
  447. height: 20.h,
  448. ),
  449. Expanded(
  450. child: ListView.builder(
  451. shrinkWrap: true,
  452. itemCount: controller.monthCount,
  453. itemBuilder: (context, index) {
  454. final monthAssets =
  455. ImageUtil.getMonthAssets(controller.assetsByMonth, index);
  456. return Column(
  457. mainAxisSize: MainAxisSize.min,
  458. crossAxisAlignment: CrossAxisAlignment.start,
  459. children: [
  460. Text(
  461. ImageUtil.getMonthText(controller.assetsByMonth, index),
  462. style: TextStyle(
  463. fontSize: 16.sp,
  464. fontWeight: FontWeight.w500,
  465. color: Colors.white),
  466. ),
  467. SizedBox(
  468. height: 11.h,
  469. ),
  470. GridView.builder(
  471. shrinkWrap: true,
  472. physics: NeverScrollableScrollPhysics(),
  473. itemCount: monthAssets.length,
  474. itemBuilder: (context, index) {
  475. var asset = monthAssets[index];
  476. return _buildAssetItem(asset);
  477. },
  478. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  479. crossAxisCount: 3, // 每行有 4 列
  480. mainAxisSpacing: 8.w, // 主轴(垂直)上的间距
  481. crossAxisSpacing: 8.w, // 交叉轴(水平)上的间距
  482. childAspectRatio: 1.0, // 子项宽高比
  483. ),
  484. ),
  485. SizedBox(
  486. height: 24.h,
  487. ),
  488. ],
  489. );
  490. },
  491. ),
  492. ),
  493. !controller.isEdit.value
  494. ? GestureDetector(
  495. onTap: () {
  496. controller.uploadBtnClick();
  497. },
  498. child: Container(
  499. width: 328.w,
  500. height: 48.h,
  501. decoration: BoxDecoration(
  502. color: "#0279FB".color,
  503. borderRadius: BorderRadius.all(
  504. Radius.circular(10.r),
  505. ),
  506. ),
  507. child: Center(
  508. child: Text(
  509. "Upload",
  510. style: TextStyle(
  511. color: Colors.white,
  512. fontSize: 16.sp,
  513. fontWeight: FontWeight.w500,
  514. ),
  515. ),
  516. ),
  517. ),
  518. )
  519. : GestureDetector(
  520. onTap: () {
  521. controller.deleteBtnClick();
  522. },
  523. child: Container(
  524. width: 328.w,
  525. height: 48.h,
  526. decoration: BoxDecoration(
  527. color: "#0279FB".color,
  528. borderRadius: BorderRadius.all(
  529. Radius.circular(10.r),
  530. ),
  531. ),
  532. child: Center(
  533. child: Row(
  534. mainAxisAlignment: MainAxisAlignment.center,
  535. children: [
  536. Assets.images.iconPrivacyPhotoDelete
  537. .image(width: 18.w, height: 18.h),
  538. SizedBox(
  539. width: 5.w,
  540. ),
  541. Text(
  542. controller.formatFileSize(
  543. controller.selectedTotalSize.value),
  544. style: TextStyle(
  545. color: Colors.white,
  546. fontSize: 16.sp,
  547. fontWeight: FontWeight.w500,
  548. ),
  549. ),
  550. ],
  551. ),
  552. ),
  553. ),
  554. )
  555. ],
  556. ),
  557. );
  558. }
  559. // 构建图片项
  560. Widget _buildAssetItem(AssetInfo asset) {
  561. return GestureDetector(
  562. onTap: () async {
  563. // 获取当前资产在列表中的索引
  564. final index = PrivacyState.imageList.indexWhere((item) => item.id == asset.id);
  565. if (index != -1) { // 确保找到了索引
  566. final result = await Get.toNamed(RoutePath.photoInfo, arguments: {
  567. "type": "privacy",
  568. "list": PrivacyState.imageList,
  569. "index": index,
  570. });
  571. // 检查返回结果并刷新
  572. if (result != null && result['deleted'] == true) {
  573. controller.loadAssets();
  574. }
  575. }
  576. },
  577. child: Stack(
  578. children: [
  579. ClipRRect(
  580. borderRadius: BorderRadius.circular(8.r),
  581. child: FutureBuilder<Uint8List?>(
  582. future: ImageUtil.getImageThumbnail(asset),
  583. builder: (context, snapshot) {
  584. if (snapshot.data != null) {
  585. return Image.memory(
  586. snapshot.data!,
  587. width: double.infinity,
  588. height: double.infinity,
  589. fit: BoxFit.cover,
  590. gaplessPlayback: true,
  591. );
  592. } else {
  593. return Container();
  594. }
  595. },
  596. ),
  597. ),
  598. // 删除按钮
  599. Visibility(
  600. visible: controller.isEdit.value,
  601. child: Positioned(
  602. right: 4.w,
  603. bottom: 4.h,
  604. child: GestureDetector(
  605. onTap: () {
  606. controller.toggleSelectAsset(asset.id);
  607. },
  608. child: Container(
  609. child: controller.selectedAssets.contains(asset.id)
  610. ? Center(
  611. child: Assets.images.iconSelected.image(
  612. width: 16.w,
  613. height: 16.h,
  614. ),
  615. )
  616. : Center(
  617. child: Assets.images.iconUnselected.image(
  618. width: 16.w,
  619. height: 16.h,
  620. ),
  621. ),
  622. ),
  623. ),
  624. ),
  625. ),
  626. ],
  627. ),
  628. );
  629. }
  630. // 显示图片详情
  631. void _showImageDetail(AssetInfo asset) {
  632. Get.dialog(
  633. Dialog(
  634. backgroundColor: Colors.transparent,
  635. child: FutureBuilder<File?>(
  636. future: ImageUtil.getImageFile(asset),
  637. builder: (context, snapshot) {
  638. if (snapshot.connectionState == ConnectionState.waiting) {
  639. return const Center(child: CircularProgressIndicator());
  640. }
  641. if (snapshot.hasData && snapshot.data != null) {
  642. return InteractiveViewer(
  643. child: Image.file(
  644. snapshot.data!,
  645. fit: BoxFit.contain,
  646. ),
  647. );
  648. }
  649. return const Center(
  650. child: Text(
  651. '加载失败',
  652. style: TextStyle(color: Colors.white),
  653. ),
  654. );
  655. },
  656. ),
  657. ),
  658. );
  659. }
  660. }