diff --git a/assets/images/custom_lock.png b/assets/images/custom_lock.png new file mode 100644 index 0000000..e0f44b6 Binary files /dev/null and b/assets/images/custom_lock.png differ diff --git a/assets/images/custom_selected.png b/assets/images/custom_selected.png new file mode 100644 index 0000000..ffe49c3 Binary files /dev/null and b/assets/images/custom_selected.png differ diff --git a/ios/Podfile b/ios/Podfile index 293e648..7dce891 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -47,12 +47,12 @@ post_install do |installer| config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', + ## dart: PermissionGroup.camera + 'PERMISSION_CAMERA=1', + ## dart: PermissionGroup.photos 'PERMISSION_PHOTOS=1', - ## dart:PermissionGroup.photosAddOnly - 'PERMISSION_PHOTOS_ADD_ONLY=1', - ## dart: PermissionGroup.appTrackingTransparency 'PERMISSION_APP_TRACKING_TRANSPARENCY=1', ] diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 231f2e7..f75ccf5 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -31,6 +31,8 @@ NSAllowsArbitraryLoads + 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