privacy_view.dart 22 KB

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