privacy_controller.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import 'dart:ffi';
  2. import 'dart:math';
  3. import 'package:clean/base/base_controller.dart';
  4. import 'package:clean/handler/event_handler.dart';
  5. import 'package:clean/model/asset_info.dart';
  6. import 'package:clean/module/privacy/privacy_state.dart';
  7. import 'package:clean/utils/expand.dart';
  8. import 'package:clean/utils/file_utils.dart';
  9. import 'package:clean/utils/image_util.dart';
  10. import 'package:clean/utils/mmkv_util.dart';
  11. import 'package:flutter/Material.dart';
  12. import 'package:flutter/cupertino.dart';
  13. import 'package:flutter_screenutil/flutter_screenutil.dart';
  14. import 'package:get/get.dart';
  15. import 'package:permission_handler/permission_handler.dart';
  16. import 'package:wechat_assets_picker/wechat_assets_picker.dart';
  17. import 'package:wechat_camera_picker/wechat_camera_picker.dart';
  18. import '../../data/consts/event_report_id.dart';
  19. import '../../dialog/photo_uploading_dialog.dart';
  20. import '../../utils/toast_util.dart';
  21. import '../image_picker/image_picker_assets.dart';
  22. import 'package:intl/intl.dart';
  23. import 'dart:typed_data';
  24. import 'dart:io';
  25. class PrivacyController extends BaseController {
  26. final String isExistPasswd = "PRIVACY_EXIST_PASSWD";
  27. final String isPublic = "PRIVACY_PUBLIC";
  28. final String privacyPasswd = "PRIVACY_PASSWD";
  29. // 是否存在密码
  30. RxBool isPrivacyExistPasswd = false.obs;
  31. // 是否公开
  32. RxBool isPrivacyPublic = false.obs;
  33. RxBool isConfirm = false.obs;
  34. // 是否为编辑状态
  35. RxBool isEdit = false.obs;
  36. // 是否在重置密码
  37. bool isReset = false;
  38. // 设置密码标题
  39. RxString passwordTitle = "Input password".obs;
  40. // 密码
  41. late var passwordStr = "".obs;
  42. late var newPasswordStr = "";
  43. // 是否为解锁状态
  44. late var isUnlock = false.obs;
  45. // 使用共享状态
  46. RxList<AssetInfo> imageList = PrivacyState.imageList;
  47. RxMap<String, List<AssetInfo>> assetsByMonth = PrivacyState.assetsByMonth;
  48. // 获取月份数量
  49. int get monthCount => assetsByMonth.length;
  50. // 获取总图片数量
  51. int get totalAssetCount =>
  52. assetsByMonth.values.fold(0, (sum, list) => sum + list.length);
  53. // 存储选中的图片ID
  54. final RxSet<String> selectedAssets = <String>{}.obs;
  55. // 是否全选
  56. RxBool isAllSelected = false.obs;
  57. // 选中图片的总容量(字节)
  58. final RxInt selectedTotalSize = 0.obs;
  59. @override
  60. void onInit() {
  61. // TODO: implement onInit
  62. super.onInit();
  63. isPrivacyExistPasswd.value = KVUtil.getBool(isExistPasswd, false);
  64. isPrivacyPublic.value = KVUtil.getBool(isPublic, false);
  65. isUnlock.value = isPrivacyPublic.value;
  66. if (isPrivacyExistPasswd.value) {
  67. passwordTitle.value = "Input password";
  68. } else {
  69. passwordTitle.value = "Create New Password";
  70. }
  71. loadAssets();
  72. }
  73. // 加载并分组图片
  74. Future<void> loadAssets() async {
  75. var newImageList = <AssetInfo>[];
  76. newImageList = await FileUtils.getAllAssets(FileType.privacy);
  77. PrivacyState.imageList.value = newImageList;
  78. PrivacyState.updateMonthlyAssets();
  79. if (newImageList.isEmpty) {
  80. isEdit.value = false;
  81. return;
  82. }
  83. // 清空现有数据
  84. assetsByMonth.clear();
  85. // 按月份分组
  86. for (var asset in newImageList) {
  87. final monthKey = ImageUtil.getMonthKey(asset.createDateTime);
  88. if (!assetsByMonth.containsKey(monthKey)) {
  89. assetsByMonth[monthKey] = [];
  90. }
  91. assetsByMonth[monthKey]!.add(asset);
  92. asset.dateTitle = ImageUtil.formatMonthKey(monthKey);
  93. }
  94. newImageList.sort((a, b) => b.createDateTime.compareTo(a.createDateTime));
  95. PrivacyState.imageList.value = newImageList;
  96. PrivacyState.updateMonthlyAssets();
  97. // 对每个月份内的图片按时间排序(新的在前)
  98. assetsByMonth.forEach((key, list) {
  99. list.sort((a, b) => b.createDateTime.compareTo(a.createDateTime));
  100. });
  101. // 打印分组结果
  102. assetsByMonth.forEach((key, assets) {
  103. print('${ImageUtil.formatMonthKey(key)}: ${assets.length} photos');
  104. });
  105. }
  106. // 处理输入密码逻辑
  107. void inputPassword(String num) {
  108. passwordStr.value = passwordStr.value + num;
  109. if (passwordStr.value.length == 4) {
  110. if (isReset) {
  111. // 二次输入密码的情况
  112. if (isConfirm.value) {
  113. // 输入错误
  114. if (passwordStr.value != newPasswordStr) {
  115. ToastUtil.show("Input Error");
  116. Future.delayed(const Duration(milliseconds: 100), () {
  117. passwordStr.value = "";
  118. });
  119. } else {
  120. isUnlock.value = true;
  121. KVUtil.putString(privacyPasswd, passwordStr.value);
  122. KVUtil.putBool(isExistPasswd, true);
  123. KVUtil.putBool(isPublic, false);
  124. isPrivacyExistPasswd.value = KVUtil.getBool(isExistPasswd, false);
  125. isPrivacyPublic.value = KVUtil.getBool(isPublic, false);
  126. isReset = false;
  127. passwordStr.value = "";
  128. }
  129. } else {
  130. // 第一次输入密码
  131. isConfirm.value = true;
  132. newPasswordStr = passwordStr.value;
  133. passwordTitle.value = "Confirm Password";
  134. Future.delayed(const Duration(milliseconds: 100), () {
  135. passwordStr.value = "";
  136. });
  137. }
  138. return;
  139. }
  140. }
  141. if (passwordStr.value.length == 4) {
  142. // 存在密码的情况
  143. if (isPrivacyExistPasswd.value) {
  144. String? password = KVUtil.getString(privacyPasswd, "");
  145. if (passwordStr.value == password) {
  146. EventHandler.report(EventId.event_05002);
  147. isUnlock.value = true;
  148. passwordStr.value = "";
  149. } else {
  150. ToastUtil.show("Input Error");
  151. Future.delayed(const Duration(milliseconds: 100), () {
  152. passwordStr.value = "";
  153. });
  154. }
  155. } else {
  156. // 二次输入密码的情况
  157. if (isConfirm.value) {
  158. // 输入错误
  159. if (passwordStr.value != newPasswordStr) {
  160. ToastUtil.show("Input Error");
  161. Future.delayed(const Duration(milliseconds: 100), () {
  162. passwordStr.value = "";
  163. });
  164. } else {
  165. EventHandler.report(EventId.event_05002);
  166. isUnlock.value = true;
  167. KVUtil.putString(privacyPasswd, passwordStr.value);
  168. KVUtil.putBool(isExistPasswd, true);
  169. KVUtil.putBool(isPublic, false);
  170. isPrivacyExistPasswd.value = KVUtil.getBool(isExistPasswd, false);
  171. isPrivacyPublic.value = KVUtil.getBool(isPublic, false);
  172. isReset = false;
  173. passwordStr.value = "";
  174. }
  175. } else {
  176. // 第一次输入密码
  177. isConfirm.value = true;
  178. newPasswordStr = passwordStr.value;
  179. passwordTitle.value = "Confirm Password";
  180. Future.delayed(const Duration(milliseconds: 100), () {
  181. passwordStr.value = "";
  182. });
  183. }
  184. return;
  185. }
  186. }
  187. }
  188. void dialogSetPassword() {
  189. // // 存在密码情况下
  190. // if (isPrivacyExistPasswd.value) {
  191. // isConfirm.value = false;
  192. // isUnlock.value = false;
  193. // passwordTitle.value = "Input password";
  194. // isReset = true;
  195. // } else {
  196. isConfirm.value = false;
  197. isUnlock.value = false;
  198. passwordTitle.value = "Create New Password";
  199. isReset = true;
  200. // }
  201. }
  202. // 设置公开状态
  203. void setPublic() {
  204. KVUtil.putString("", passwordStr.value);
  205. KVUtil.putBool(isExistPasswd, false);
  206. KVUtil.putBool(isPublic, true);
  207. isPrivacyExistPasswd.value = KVUtil.getBool(isExistPasswd, false);
  208. isPrivacyPublic.value = KVUtil.getBool(isPublic, false);
  209. }
  210. // 上传按钮点击
  211. void uploadBtnClick() {
  212. EventHandler.report(EventId.event_05002);
  213. showCupertinoModalPopup(
  214. context: Get.context!,
  215. builder: (context) {
  216. return CupertinoActionSheet(
  217. actions: <Widget>[
  218. //操作按钮集合
  219. CupertinoActionSheetAction(
  220. onPressed: () {
  221. Navigator.pop(context);
  222. openGallery();
  223. },
  224. child: Text(
  225. 'Upload from Gallery',
  226. style: TextStyle(
  227. color: "#007AFF".color,
  228. fontWeight: FontWeight.w500,
  229. fontSize: 16.sp,
  230. ),
  231. ),
  232. ),
  233. CupertinoActionSheetAction(
  234. onPressed: () {
  235. Navigator.pop(context);
  236. openCamera();
  237. },
  238. child: Text(
  239. 'Take and Upload',
  240. style: TextStyle(
  241. color: "#007AFF".color,
  242. fontWeight: FontWeight.w500,
  243. fontSize: 16.sp,
  244. ),
  245. ),
  246. ),
  247. ],
  248. cancelButton: CupertinoActionSheetAction(
  249. //取消按钮
  250. onPressed: () {
  251. Navigator.pop(context);
  252. },
  253. child: Text(
  254. 'Cancel',
  255. style: TextStyle(
  256. color: "#007AFF".color,
  257. fontWeight: FontWeight.w500,
  258. fontSize: 16.sp,
  259. ),
  260. ),
  261. ),
  262. );
  263. },
  264. );
  265. }
  266. // 保存并刷新图片列表
  267. Future<void> saveAndRefreshAssets(List<AssetEntity> assets) async {
  268. for (var asset in assets) {
  269. await FileUtils.saveAsset(FileType.privacy, asset);
  270. }
  271. showDialog(
  272. context: Get.context!,
  273. barrierDismissible: false, // 防止点击外部关闭
  274. builder: (context) => UploadingDialog(
  275. duration: const Duration(seconds: 3),
  276. onComplete: () {
  277. loadAssets();
  278. ToastUtil.show("Successful");
  279. // 这里可以添加完成后的操作
  280. },
  281. ),
  282. );
  283. // 重新加载图片列表
  284. // loadAssets();
  285. }
  286. // 选择/取消选择图片
  287. void toggleSelectAsset(String assetId) {
  288. final asset = imageList.firstWhere((asset) => asset.id == assetId);
  289. if (selectedAssets.contains(assetId)) {
  290. selectedAssets.remove(assetId);
  291. if (asset.size != null) {
  292. selectedTotalSize.value -= asset.size!;
  293. }
  294. } else {
  295. selectedAssets.add(assetId);
  296. if (asset.size != null) {
  297. selectedTotalSize.value += asset.size!;
  298. }
  299. }
  300. // 更新全选状态
  301. isAllSelected.value = selectedAssets.length == imageList.length;
  302. }
  303. // 全选/取消全选
  304. void toggleSelectAll() {
  305. if (isAllSelected.value) {
  306. selectedAssets.clear();
  307. selectedTotalSize.value = 0;
  308. } else {
  309. selectedAssets.addAll(imageList.map((asset) => asset.id));
  310. selectedTotalSize.value = imageList.fold(
  311. 0, (sum, asset) => sum + (asset.size != null ? asset.size! : 0));
  312. }
  313. isAllSelected.value = !isAllSelected.value;
  314. }
  315. // 退出编辑模式时清空选择
  316. void exitEditMode() {
  317. isEdit.value = false;
  318. selectedAssets.clear();
  319. isAllSelected.value = false;
  320. selectedTotalSize.value = 0;
  321. }
  322. // 删除文件
  323. void deleteBtnClick() {
  324. EventHandler.report(EventId.event_05004);
  325. // 获取要删除的资产
  326. final assetsToDelete =
  327. imageList.where((asset) => selectedAssets.contains(asset.id)).toList();
  328. for (var asset in assetsToDelete) {
  329. FileUtils.deleteAsset(FileType.privacy, Platform.isIOS ? asset.id.substring(0, 36) : asset.id);
  330. }
  331. selectedTotalSize.value = 0;
  332. loadAssets();
  333. }
  334. // 格式化文件大小显示
  335. String formatFileSize(int bytes) {
  336. if (bytes <= 0) return "Delete";
  337. final units = ['B', 'KB', 'MB', 'GB'];
  338. int digitGroups = (log(bytes) / log(1024)).floor();
  339. if (bytes == 0) {
  340. return "Delete";
  341. } else {
  342. return "Delete(${(bytes / pow(1024, digitGroups)).toStringAsFixed(1)} ${units[digitGroups]})";
  343. }
  344. }
  345. // 开启图库
  346. Future<void> openGallery() async {
  347. var status = await Permission.photos.status;
  348. if (status == PermissionStatus.granted) {
  349. List<AssetEntity> assets = <AssetEntity>[];
  350. for (var asset in imageList) {
  351. var newAsset = await asset.toAssetEntity();
  352. if (newAsset != null) {
  353. assets.add(newAsset);
  354. }
  355. }
  356. List<AssetEntity>? pickList = await ImagePickAssets.pick();
  357. if (pickList != null && pickList.isNotEmpty) {
  358. await saveAndRefreshAssets(pickList);
  359. }
  360. } else {
  361. var result = await Permission.photos.request();
  362. if (result.isGranted) {
  363. openGallery();
  364. } else if (result.isPermanentlyDenied) {
  365. ToastUtil.show("Please enable the camera permission first.");
  366. openAppSettings();
  367. } else{
  368. ToastUtil.show("Please enable the camera permission first.");
  369. }
  370. }
  371. }
  372. // 开启相机
  373. Future<void> openCamera() async {
  374. var status = await Permission.camera.status;
  375. if (status == PermissionStatus.granted) {
  376. final entity = await CameraPicker.pickFromCamera(
  377. Get.context!,
  378. pickerConfig: const CameraPickerConfig(),
  379. );
  380. if (entity != null) {
  381. await saveAndRefreshAssets([entity]);
  382. }
  383. } else if (status.isPermanentlyDenied) {
  384. ToastUtil.show("Please enable the camera permission first.");
  385. openAppSettings();
  386. } else{
  387. ToastUtil.show("Please enable the camera permission first.");
  388. }
  389. }
  390. }