From 121e423c8bbfa44e8be243c9f9bf411af5161997 Mon Sep 17 00:00:00 2001 From: xuhang-x <36baea72@gmail.com> Date: Wed, 24 Jul 2024 20:13:27 +0800 Subject: [PATCH] 1 --- assets/images/custom_lock.png | Bin 0 -> 16129 bytes assets/images/custom_selected.png | Bin 0 -> 1267 bytes ios/Podfile | 6 +- ios/Runner/Info.plist | 2 + .../navigation_bar/custom_appbar.dart | 17 +- .../components/photo_picker_bottom_sheet.dart | 66 +++++ .../pin_code_verification_screen.dart | 253 ++++++++++++++++++ lib/common/storage/custom_data.dart | 41 +++ lib/common/storage/hive_storage.dart | 7 + lib/generated/assets.dart | 2 + lib/page/custom/custom_controller.dart | 230 ++++++++++++++++ lib/page/custom/custom_view.dart | 129 +++++++++ lib/page/home/home_controller.dart | 2 + .../wallpaper_detail_controller.dart | 4 +- pubspec.yaml | 5 + 15 files changed, 748 insertions(+), 16 deletions(-) create mode 100644 assets/images/custom_lock.png create mode 100644 assets/images/custom_selected.png create mode 100644 lib/common/components/photo_picker_bottom_sheet.dart create mode 100644 lib/common/components/pin_code_verification_screen.dart create mode 100644 lib/common/storage/custom_data.dart create mode 100644 lib/page/custom/custom_controller.dart create mode 100644 lib/page/custom/custom_view.dart diff --git a/assets/images/custom_lock.png b/assets/images/custom_lock.png new file mode 100644 index 0000000000000000000000000000000000000000..e0f44b6e81a7c4aa0c679c69c2f0c2c1058a50da GIT binary patch literal 16129 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 literal 0 HcmV?d00001 diff --git a/assets/images/custom_selected.png b/assets/images/custom_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..ffe49c31948f2c087c68e972a55670a46ec07258 GIT binary patch literal 1267 zcmV2E)4m000DZQchC z)n@5ke@dzbeW#(D6j|s-%{&m-SW-1B0<-LzxmjErFw3sU&EhJc5WCKx5YO!M^N{@! zqpvSeBGlO=D(Dg}YULH_g@RS31Z5W$#9}BKdf+xh2PTwXDZPT~3Deh}YKu zK93?^BI61ZFZoYLFe3TVb#fLnN*IFsD1Gt^3Qzo6VjJD(x==uy~M7i|-# z1ZbeJONhlZrw4Wkkt(CW`q#t1LWED0#D@DrCH$p)7q<{39E&`#VOUg-e!T010^E`> z_eTRC-`^&Im%*pQ`%e;LfY*-c_$n0230`Zb<2$fW5`e4^mPuoE#84@O>{;Ot5HbL_Y?MtPny1fEihPHh5;=sA5F@0>u0p3ew6C130~d$zfeUS3C+t>NIfT!^ z9I`n4XoVq7x618<#`a#n7#p~BijN8eHE?`b&wcUgC3IsK9uRwwd z;SIb4$SM%lz&omBvF*SGWwO8q)^3j^1Zn?ENxMB#6$o)lt8y{zU8{D!D&q%D6+pla z49+LKfjNXn!Xex(4(VwFbIk$)Zb8WxK?8Hh;2ud4h(`m9?I036BxASU=I)+CZePyj z!wuXhQ7FoVDRnD$Eu|)8p$$wQdpQf_a3qC7V*AA0bZeu}v(8P(ra@Tpf$&Vqy3|W;y+2WSEs{5}23DqoZrM{|xH|>`MFhZygmd=#U_PsOzMjV6z4pvhJ3sS0RfA$@Kw5aU`mTaBm&%{7Lqx9>TphQ3{8s zn`{vVfb{xcl^T$M9%WDZtlNKrNMnh1oV)t>MHufDSs|OgSW9Drzl=@EJE-E8zE}%? z#fjhEDQ1S+jhWvf; zZu!4Z;T`?|c*$i4BaO$KN7*5Ezfz(C85|1{Exv{DQg|V}6kdoZ`xPSU9SU(+_B(nd z*S^@@OL?QOV%t;4+4I!7VcaZk7&pt?u-E}5vsvzxy<-ZtK-?*1?y#;rbb}Ip-P4^C zJEKO(uv0jLQ+Qu-g#*D%!muiVy~M67u*$AsY$HG!07DQQlRxDR{$Cxyk)O1t%dKNSAllowsArbitraryLoads + NSCameraUsageDescription + This will enable you to take photos and recognize text within them for translation. NSPhotoLibraryAddUsageDescription We need access to your photo album so you can save wallpapers from the app to your album NSPhotoLibraryUsageDescription diff --git a/lib/common/components/navigation_bar/custom_appbar.dart b/lib/common/components/navigation_bar/custom_appbar.dart index d9c60b8..7862fd0 100644 --- a/lib/common/components/navigation_bar/custom_appbar.dart +++ b/lib/common/components/navigation_bar/custom_appbar.dart @@ -24,7 +24,7 @@ class CustomAppbar extends StatelessWidget implements PreferredSizeWidget { Widget build(BuildContext context) { return Container( height: preferredSize.height, - padding: EdgeInsets.fromLTRB(10, ScreenUtil().statusBarHeight, 20, 0).w, + padding: EdgeInsets.fromLTRB(10, ScreenUtil().statusBarHeight, 10, 0).w, color: backgroundColor ?? seedColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -35,14 +35,11 @@ class CustomAppbar extends StatelessWidget implements PreferredSizeWidget { color: Colors.transparent, child: InkWell( onTap: onBackTap ?? () => Get.back(), - child: Padding( - padding: const EdgeInsets.all(10).w, - child: Image.asset( - Assets.iconBack, - width: 32.w, - height: 32.w, - color: Colors.white, - ), + child: Image.asset( + Assets.iconBack, + width: 32.w, + height: 32.w, + color: Colors.white, ), ), ), @@ -60,7 +57,7 @@ class CustomAppbar extends StatelessWidget implements PreferredSizeWidget { ), ), ), - Container(width: 13.w), + Container(width: 32.w), ], ), ); diff --git a/lib/common/components/photo_picker_bottom_sheet.dart b/lib/common/components/photo_picker_bottom_sheet.dart new file mode 100644 index 0000000..2aeed99 --- /dev/null +++ b/lib/common/components/photo_picker_bottom_sheet.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class TPhotoPickerBottomSheet extends StatelessWidget { + const TPhotoPickerBottomSheet({super.key, required this.funCamera, required this.funGallery}); + + final Function() funCamera; + final Function() funGallery; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Container( + width: MediaQuery.of(context).size.width, + height: 120, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(26), + ), + child: Column( + children: [ + Expanded( + child: _buildItem('Open camera', funCamera), + ), + const Divider( + height: 1, + thickness: 1, + color: Color(0xFFEDEDED), + ), + Expanded( + child: _buildItem('Open gallery', funGallery), + ), + ], + ), + ), + ); + } + + Widget _buildItem(String text, Function() function) { + return Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Get.back(); + function(); + }, + child: SizedBox( + width: double.infinity, + child: Center( + child: Text( + text, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Color(0xFF333333), + fontSize: 16, + fontWeight: FontWeight.w600, + ) + ), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/common/components/pin_code_verification_screen.dart b/lib/common/components/pin_code_verification_screen.dart new file mode 100644 index 0000000..536406b --- /dev/null +++ b/lib/common/components/pin_code_verification_screen.dart @@ -0,0 +1,253 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:pin_code_fields/pin_code_fields.dart'; +import 'package:wallpaperx/common/utils/shared_util.dart'; +import 'package:wallpaperx/generated/assets.dart'; + +class PinCodeVerificationScreen extends StatefulWidget { + final Function callback; + final String checkPassword; + + const PinCodeVerificationScreen({ + super.key, + required this.callback, + required this.checkPassword, + }); + + @override + State createState() => + _PinCodeVerificationScreenState(); +} + +class _PinCodeVerificationScreenState extends State { + var onTapRecognizer; + + TextEditingController textEditingController = TextEditingController(); + + late StreamController errorController; + + bool hasError = false; + String currentText = ""; + final GlobalKey scaffoldKey = GlobalKey(); + final formKey = GlobalKey(); + + @override + void initState() { + onTapRecognizer = TapGestureRecognizer() + ..onTap = () { + Navigator.pop(context); + }; + errorController = StreamController(); + super.initState(); + } + + @override + void dispose() { + errorController.close(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + key: scaffoldKey, + body: SafeArea( + child: Container( + margin: EdgeInsets.only(top: 94.w), + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Please input a', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 28.sp, + color: Colors.white, + ), + ), + Text( + 'password', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 28.sp, + color: Colors.white, + ), + ) + ], + ), + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15.r), + ), + child: Image.asset( + Assets.imagesCustomLock, + width: 100.w, + height: 100.w, + ), + ), + ], + ), + SizedBox(height: 64.w), + Form( + key: formKey, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 30, + ).w, + child: PinCodeTextField( + appContext: context, + pastedTextStyle: TextStyle( + color: Colors.green.shade600, + fontWeight: FontWeight.bold, + ), + dialogConfig: DialogConfig(platform: PinCodePlatform.iOS), + length: 4, + obscureText: true, + obscuringCharacter: '*', + animationType: AnimationType.fade, + pinTheme: PinTheme( + activeColor: const Color(0xff262626), + selectedColor: const Color(0xff262626), + selectedFillColor: Colors.grey, + inactiveFillColor: const Color(0xff262626), + errorBorderColor: const Color(0xff262626), + inactiveColor: const Color(0xff262626), + shape: PinCodeFieldShape.box, + borderRadius: BorderRadius.circular(30.r), + fieldHeight: 58.w, + fieldWidth: 58.w, + activeFillColor: hasError + ? const Color(0xff262626) + : const Color(0xff262626), + ), + showCursor: false, + animationDuration: const Duration(milliseconds: 300), + textStyle: TextStyle( + fontSize: 32.sp, + fontWeight: FontWeight.w700, + color: Colors.white, + height: 2.0, + ), + backgroundColor: Colors.transparent, + enableActiveFill: true, + errorAnimationController: errorController, + controller: textEditingController, + keyboardType: TextInputType.number, + boxShadows: const [ + BoxShadow( + offset: Offset(0, 1), + color: Colors.black12, + blurRadius: 30, + ), + ], + onCompleted: (v) {}, + onChanged: (value) { + setState(() { + currentText = value; + }); + }, + beforeTextPaste: (text) { + setState(() { + currentText = text!; + }); + return true; + }, + )), + ), + Visibility( + visible: widget.checkPassword == "", + child: Text( + "First input as password", + style: TextStyle( + color: Colors.red, + fontSize: 14.sp, + ), + ), + ), + GestureDetector( + onTap: () { + formKey.currentState?.validate(); + if (currentText.length == 4 && widget.checkPassword == "") { + UPCache.getInstance() + .setData("custom_password", currentText); + widget.callback(); + } else if (currentText.length != 4 || + currentText != widget.checkPassword) { + errorController.add(ErrorAnimationType + .shake); // Triggering error shake animation + setState(() { + hasError = true; + textEditingController.clear(); + }); + } else { + widget.callback(); + setState(() { + hasError = false; + }); + } + }, + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 24, + horizontal: 40, + ).w, + padding: const EdgeInsets.symmetric( + vertical: 17, + ).w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.r), + gradient: const LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + Color(0xffBEEF32), + Color(0xff2795E5), + Color(0xff8041FD), + ], + ), + ), + child: Center( + child: Text( + "Confirm", + style: TextStyle( + color: Colors.white, + fontSize: 18.sp, + fontWeight: FontWeight.bold), + ), + ), + ), + ), + GestureDetector( + child: Text( + "Clear", + style: TextStyle( + color: Colors.white, + fontSize: 18.sp, + ), + ), + onTap: () { + textEditingController.clear(); + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/common/storage/custom_data.dart b/lib/common/storage/custom_data.dart new file mode 100644 index 0000000..cbd3c27 --- /dev/null +++ b/lib/common/storage/custom_data.dart @@ -0,0 +1,41 @@ +import 'package:wallpaperx/common/storage/hive_storage.dart'; +import 'package:wallpaperx/entity/image_model.dart'; + +class CustomData { + /// 私有构造函数 + CustomData._(); + + /// 静态常量用于保存类的唯一实例 + static final CustomData _instance = CustomData._(); + + /// 工厂构造函数返回类的唯一实例 + factory CustomData() { + return _instance; + } + + /// 声明盒子 + /// 注意, main函数中这个盒子已经打开, 可以进行存储操作 + final _box = getCustomBox(); + + /// 获取壁纸 + List getWallpaperData() { + return _box.values.toList(); + } + + /// 存储壁纸 + Future setWallpaperData(ImageModel wallpaperData) async { + return await _box.add(wallpaperData); + } + + /// 删除壁纸 + Future delete(index) async { + await _box.deleteAt(index); + await _box.flush(); + } + + /// 删除所有壁纸 + Future clear() async { + await _box.clear(); + await _box.flush(); + } +} diff --git a/lib/common/storage/hive_storage.dart b/lib/common/storage/hive_storage.dart index e0fc08a..735c99b 100644 --- a/lib/common/storage/hive_storage.dart +++ b/lib/common/storage/hive_storage.dart @@ -5,6 +5,7 @@ import 'package:wallpaperx/entity/tags_model.dart'; const favoriteBox = 'favoriteBox'; const historyBox = 'historyBox'; +const customBox = 'customBox'; Future initHive() async { // 初始化 @@ -16,6 +17,7 @@ Future initHive() async { // 打开盒子 await Hive.openBox(favoriteBox); await Hive.openBox(historyBox); + await Hive.openBox(customBox); } /// 获取盒子 @@ -26,4 +28,9 @@ Box getFavoriteBox() { /// 获取盒子 Box getHistoryBox() { return Hive.box(historyBox); +} + +/// 获取盒子 +Box getCustomBox() { + return Hive.box(customBox); } \ No newline at end of file diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 956745c..1da0d31 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -46,6 +46,8 @@ class Assets { static const String iconUnFavorite = 'assets/icon/un_favorite.png'; static const String iconUp = 'assets/icon/up.png'; static const String imagesCollectionSelected = 'assets/images/collection_selected.png'; + static const String imagesCustomLock = 'assets/images/custom_lock.png'; + static const String imagesCustomSelected = 'assets/images/custom_selected.png'; static const String imagesRecommendBottomBackground = 'assets/images/recommend_bottom_background.png'; static const String imagesRecommendSelected = 'assets/images/recommend_selected.png'; static const String imagesRecommendTopBackground = 'assets/images/recommend_top_background.png'; diff --git a/lib/page/custom/custom_controller.dart b/lib/page/custom/custom_controller.dart new file mode 100644 index 0000000..9a9c669 --- /dev/null +++ b/lib/page/custom/custom_controller.dart @@ -0,0 +1,230 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:crop_your_image/crop_your_image.dart'; +import 'package:flip_card/flip_card_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:pin_code_fields/pin_code_fields.dart'; +import 'package:uuid/uuid.dart'; +import 'package:wallpaperx/common/components/dialog/remind_dialog.dart'; +import 'package:wallpaperx/common/components/easy_loading.dart'; +import 'package:wallpaperx/common/components/navigation_bar/custom_appbar.dart'; +import 'package:wallpaperx/common/components/photo_picker_bottom_sheet.dart'; +import 'package:wallpaperx/common/components/view_state_widget.dart'; +import 'package:wallpaperx/common/storage/custom_data.dart'; +import 'package:wallpaperx/common/utils/device_info_util.dart'; +import 'package:wallpaperx/common/utils/log_print.dart'; +import 'package:wallpaperx/common/utils/permission_util.dart'; +import 'package:wallpaperx/common/utils/shared_util.dart'; +import 'package:wallpaperx/entity/image_model.dart'; +import 'package:wallpaperx/routes/app_pages.dart'; + +class CustomController extends GetxController { + static CustomController get to => Get.find(); + late ScrollController scrollController; + late ViewState viewState; + RxList customList = [].obs; + + String password = ""; + + TextEditingController textEditingController = TextEditingController(); + + late StreamController errorController; + + late FlipCardController flipCardController; + + bool hasError = false; + + @override + void onInit() { + super.onInit(); + errorController = StreamController(); + scrollController = ScrollController(); + flipCardController = FlipCardController(); + password = UPCache.getInstance().get("custom_password")??""; + getCustomList(); + } + + @override + void onClose() { + scrollController.dispose(); + super.onClose(); + } + + void getCustomList() { + customList.clear(); + customList.addAll(CustomData().getWallpaperData().reversed.toList()); + LogPrint.d(customList.length); + refreshCustomList(); + } + + void refreshCustomList() { + viewState = customList.isNotEmpty ? ViewState.normal : ViewState.empty; + refresh(); + } + + /// 点击壁纸 + void toImageDetail(int position) { + Get.toNamed(AppPages.wallpaperDetail, arguments: { + 'position': position, + 'isSetHistory': false, + 'wallpaperList': customList, + }); + } + + /// 长按壁纸 + void onLongPressImage(int position) { + Get.dialog( + barrierDismissible: false, + RemindDialog( + content: 'Are you sure you want to delete the record?', + confirmOnTap: () { + // 计算原始列表中的对应索引 + int indexToRemoveFromDb = + CustomData().getWallpaperData().length - 1 - position; + CustomData().delete(indexToRemoveFromDb); + customList.removeAt(position); + refreshCustomList(); + }, + ), + ); + } + + Future toPhotos() async { + Get.bottomSheet( + isScrollControlled: true, + TPhotoPickerBottomSheet( + funCamera: () async { + bool result = + await PermissionUtil.checkPermission([Permission.camera]); + if (!result) return; + _openCameraGallery(ImageSource.camera); + }, + funGallery: () async { + Permission permission = Permission.photos; + if (Platform.isAndroid) { + int sdkInt = await DeviceInfoUtil.getAndroidSystemVersion(); + if (sdkInt <= 32) { + permission = Permission.storage; + } else { + permission = Permission.photos; + } + } + bool result = await PermissionUtil.checkPermission([permission]); + if (!result) return; + _openCameraGallery(ImageSource.gallery); + }, + ), + ); + } + + Future _openCameraGallery(ImageSource source) async { + final ImagePicker picker = ImagePicker(); + final XFile? photo = await picker.pickImage(source: source); + if (photo != null) { + final controller = CropController(); + Uint8List image = await photo.readAsBytes(); + Get.to( + () => Scaffold( + backgroundColor: Colors.black, + appBar: const CustomAppbar(''), + body: SafeArea( + top: false, + child: Column( + children: [ + Expanded( + child: Crop( + baseColor: Colors.black, + image: image, + controller: controller, + onCropped: (image) { + setFile(image); + LogPrint.d('裁剪后的图片:${image.lengthInBytes}'); + }, + ), + ), + Row( + children: [ + Expanded(child: Container()), + GestureDetector( + onTap: controller.crop, + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 46, + ).w, + padding: const EdgeInsets.symmetric( + horizontal: 35, + vertical: 8, + ).w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.r), + gradient: const LinearGradient( + begin: Alignment.topRight, + end: Alignment.bottomLeft, + colors: [ + Color(0xffBEEF32), + Color(0xff2795E5), + Color(0xff8041FD), + ], + ), + ), + child: Center( + child: Text( + "Apply", + style: TextStyle( + color: Colors.white, + fontSize: 18.sp, + fontWeight: FontWeight.bold), + ), + ), + ), + ) + ], + ), + ], + ), + ), + ), + ); + } + } + + Future setFile(Uint8List image) async { + try { + String uuid = const Uuid().v4(); + Directory directory = await getApplicationDocumentsDirectory(); + File file = File('${directory.path}/$uuid.txt'); + // 保存到文件系统 + await file.writeAsBytes(image); + // 询问用户是否保存它 + ImageModel model = ImageModel(imageUrl: file.path); + CustomData().setWallpaperData(model); + Get.back(); + getCustomList(); + } catch (e) { + toast(e.toString()); + } + } + + Uint8List? readImage(String filePath) { + try { + final file = File(filePath); + Uint8List u8 = file.readAsBytesSync(); + return u8; + } catch (e) { + return null; + } + } + + /// 翻转 + void flipCard() { + flipCardController.toggleCard(); + } +} diff --git a/lib/page/custom/custom_view.dart b/lib/page/custom/custom_view.dart new file mode 100644 index 0000000..df639c3 --- /dev/null +++ b/lib/page/custom/custom_view.dart @@ -0,0 +1,129 @@ +import 'dart:typed_data'; + +import 'package:flip_card/flip_card.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:get/get.dart'; +import 'package:wallpaperx/common/components/navigation_bar/custom_appbar.dart'; +import 'package:wallpaperx/common/components/pin_code_verification_screen.dart'; +import 'package:wallpaperx/common/components/view_state_widget.dart'; +import 'package:wallpaperx/entity/image_model.dart'; +import 'package:wallpaperx/generated/assets.dart'; + +import 'custom_controller.dart'; + +class CustomView extends GetView { + const CustomView({super.key}); + + @override + Widget build(BuildContext context) { + Get.put(CustomController()); + return Scaffold( + backgroundColor: Colors.black, + body: FlipCard( + flipOnTouch: false, + controller: controller.flipCardController, + fill: Fill.fillBack, + side: CardSide.FRONT, + direction: FlipDirection.HORIZONTAL, + front: PinCodeVerificationScreen( + callback: controller.flipCard, + checkPassword: controller.password, + ), + back: _buildCustomWidget(context), + ), + ); + } + + Widget _buildCustomWidget(context) { + return Stack( + children: [ + Container( + width: double.infinity, + height: double.infinity, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.imagesSettingBackground), + fit: BoxFit.cover, + ), + ), + child: Column( + children: [ + CustomAppbar( + "Custom", + backgroundColor: Colors.transparent, + backWidget: Container(width: 32.w), + titleStyle: TextStyle( + color: Colors.white, + fontSize: 24.sp, + fontWeight: FontWeight.w600, + ), + ), + Expanded(child: GetBuilder( + builder: (logic) { + return ViewStateWidget( + viewState: controller.viewState, + child: MediaQuery.removePadding( + context: context, + removeTop: true, + child: Scrollbar( + controller: controller.scrollController, + child: MasonryGridView.count( + controller: controller.scrollController, + itemCount: controller.customList.length, + crossAxisCount: 2, + mainAxisSpacing: 15.w, + crossAxisSpacing: 15.w, + padding: const EdgeInsets.fromLTRB(15, 20, 15, 0).w, + physics: const BouncingScrollPhysics(), + itemBuilder: (context, index) { + ImageModel item = controller.customList[index]; + Uint8List? image = + controller.readImage(item.imageUrl ?? ""); + if (image == null) return Container(); + return GestureDetector( + onTap: () { + Get.to( + () => Scaffold( + backgroundColor: Colors.black, + appBar: const CustomAppbar(''), + body: Container( + alignment: Alignment.center, + child: Image.memory(image), + ), + ), + ); + }, + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(13.r), + ), + child: Image.memory(image), + ), + ); + }, + ), + ), + ), + ); + }, + )) + ], + ), + ), + Positioned( + right: 10.w, + bottom: MediaQuery.of(context).padding.bottom + 80.w, + child: FloatingActionButton( + backgroundColor: Colors.grey, + onPressed: controller.toPhotos, + child: const Icon(Icons.add), + ), + ) + ], + ); + } +} diff --git a/lib/page/home/home_controller.dart b/lib/page/home/home_controller.dart index 79c54ee..af58f76 100644 --- a/lib/page/home/home_controller.dart +++ b/lib/page/home/home_controller.dart @@ -5,6 +5,7 @@ import 'package:wallpaperx/common/utils/shared_util.dart'; import 'package:wallpaperx/config/app_tracking_transparency_manager.dart'; import 'package:wallpaperx/entity/userinfo_model.dart'; import 'package:wallpaperx/generated/assets.dart'; +import 'package:wallpaperx/page/custom/custom_view.dart'; import 'package:wallpaperx/page/library/library_view.dart'; import 'package:wallpaperx/page/recommend/recommend_view.dart'; import 'package:wallpaperx/page/settings/settings_view.dart'; @@ -15,6 +16,7 @@ class HomeController extends GetxController with WidgetsBindingObserver { final pages = [ PageItem(Assets.imagesRecommendSelected, const RecommendView()), PageItem(Assets.imagesCollectionSelected, const LibraryView()), + PageItem(Assets.imagesCustomSelected, const CustomView()), PageItem(Assets.imagesSettingSelected, const SettingsView()), ]; late PageController pageController; diff --git a/lib/page/wallpaper_detail/wallpaper_detail_controller.dart b/lib/page/wallpaper_detail/wallpaper_detail_controller.dart index 1e6f0de..e0ecf35 100644 --- a/lib/page/wallpaper_detail/wallpaper_detail_controller.dart +++ b/lib/page/wallpaper_detail/wallpaper_detail_controller.dart @@ -85,9 +85,7 @@ class WallpaperDetailController extends GetxController { filePath = savePath; bool canSave = true; if (Platform.isIOS) { - canSave = await PermissionUtil.checkPermission( - [Permission.photosAddOnly], - ); + canSave = await PermissionUtil.checkPermission([Permission.photos]); } if (canSave) { final result = diff --git a/pubspec.yaml b/pubspec.yaml index 23b897d..b7698e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,6 +101,11 @@ dependencies: app_tracking_transparency: ^2.0.5 gradient_borders: ^1.0.1 stacked_animated_list: ^1.0.1 + pin_code_fields: ^8.0.1 + image_picker: ^1.1.2 + uuid: ^4.4.2 + flutter_file_dialog: ^3.0.2 + crop_your_image: ^1.1.0 # Firebase firebase_core: ^2.32.0