From 1a271ba9b0564b47bebe1c1e5c9b6acdd8bcfd69 Mon Sep 17 00:00:00 2001 From: fengshengxiong Date: Mon, 13 May 2024 13:44:27 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E7=89=88=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle | 3 + android/app/src/main/AndroidManifest.xml | 35 +- .../res/drawable-v21/launch_background.xml | 12 +- .../main/res/drawable/launch_background.xml | 11 +- assets/images/error.png | Bin 0 -> 2596 bytes assets/images/placeholder.png | Bin 0 -> 1381 bytes assets/json/wallpaper.json | 12208 ++++++++++++++++ ios/Runner.xcodeproj/project.pbxproj | 132 + .../contents.xcworkspacedata | 3 + .../AppIcon.appiconset/Contents.json | 118 +- .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes ios/Runner/Assets.xcassets/Contents.json | 6 + .../LaunchImage.imageset/Contents.json | 8 +- .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - ios/Runner/Base.lproj/LaunchScreen.storyboard | 20 +- ios/Runner/Base.lproj/Main.storyboard | 13 +- ios/Runner/Info.plist | 2 + lib/common/components/base_appbar.dart | 70 + .../components/base_masonry_gridview.dart | 56 + lib/common/components/dialog/hint_dialog.dart | 117 + .../components/dialog/process_dialog.dart | 94 + lib/common/components/divider_widget.dart | 22 + lib/common/components/easy_loading.dart | 30 +- lib/common/components/get_bind_widget.dart | 95 + lib/common/components/image_widget.dart | 71 + lib/common/components/keep_alive_wrapper.dart | 30 + lib/common/components/title_bar_widget.dart | 69 + lib/common/components/view_state_widget.dart | 75 + lib/common/components/wallpaper_picker.dart | 80 + lib/common/data/api/api.dart | 8 - lib/common/models/base_resp_model.dart | 22 + lib/common/models/wallpaper_model.dart | 50 + lib/common/models/wallpaper_model.g.dart | 49 + lib/common/network/base_error.dart | 39 + lib/common/network/dio_client.dart | 169 + lib/common/network/dio_interceptor.dart | 31 + lib/common/storage/favorite_data.dart | 44 + lib/common/storage/hive_storage.dart | 22 + lib/common/theme/colors.dart | 16 - lib/common/theme/sizes.dart | 3 - lib/common/theme/themes.dart | 33 - lib/common/utils/device_info_util.dart | 15 + lib/common/utils/download_util.dart | 78 + lib/common/utils/filesize_util.dart | 70 + lib/common/utils/local_path_util.dart | 28 + lib/{ => common}/utils/log_print.dart | 2 +- lib/common/utils/num_util.dart | 28 + lib/{ => common}/utils/obj_util.dart | 2 +- lib/common/utils/permission_util.dart | 145 + lib/generated/assets.dart | 9 + .../json/base/json_convert_content.dart | 186 + lib/generated/json/base/json_field.dart | 32 + lib/generated/json/base_resp_model.g.dart | 40 + lib/generated/json/wallpaper_model.g.dart | 84 + lib/main.dart | 27 +- lib/modules/about/about_binding.dart | 10 + lib/modules/about/about_controller.dart | 19 + lib/modules/about/about_view.dart | 35 + lib/modules/favorite/favorite_controller.dart | 74 + lib/modules/favorite/favorite_view.dart | 37 + lib/modules/home/home_binding.dart | 10 - .../home/home_cls/home_cls_controller.dart | 19 + lib/modules/home/home_cls/home_cls_view.dart | 36 + lib/modules/home/home_controller.dart | 37 +- lib/modules/home/home_view.dart | 42 +- lib/modules/me/me_controller.dart | 16 + lib/modules/me/me_view.dart | 66 + lib/modules/root/root_binding.dart | 9 + lib/modules/root/root_controller.dart | 46 + lib/modules/root/root_view.dart | 36 + .../wallpaper_det/wallpaper_det_binding.dart | 10 + .../wallpaper_det_controller.dart | 105 + .../wallpaper_det/wallpaper_det_view.dart | 95 + lib/modules/web_page/web_page_binding.dart | 10 + lib/modules/web_page/web_page_controller.dart | 38 + lib/modules/web_page/web_page_view.dart | 23 + lib/res/themes/app_colors.dart | 8 + lib/res/themes/app_sizes.dart | 9 + .../themes/app_styles.dart} | 0 lib/res/themes/app_themes.dart | 48 + .../config => res/values}/strings.dart | 0 lib/routes/app_pages.dart | 43 +- lib/utils/screen_adapter.dart | 57 - pubspec.yaml | 42 +- 101 files changed, 15268 insertions(+), 329 deletions(-) create mode 100644 assets/images/error.png create mode 100644 assets/images/placeholder.png create mode 100644 assets/json/wallpaper.json delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/Contents.json create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/Icon-App-1024x1024@1x.png delete mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 lib/common/components/base_appbar.dart create mode 100644 lib/common/components/base_masonry_gridview.dart create mode 100644 lib/common/components/dialog/hint_dialog.dart create mode 100644 lib/common/components/dialog/process_dialog.dart create mode 100644 lib/common/components/divider_widget.dart create mode 100644 lib/common/components/get_bind_widget.dart create mode 100644 lib/common/components/image_widget.dart create mode 100644 lib/common/components/keep_alive_wrapper.dart create mode 100644 lib/common/components/title_bar_widget.dart create mode 100644 lib/common/components/view_state_widget.dart create mode 100644 lib/common/components/wallpaper_picker.dart delete mode 100644 lib/common/data/api/api.dart create mode 100644 lib/common/models/base_resp_model.dart create mode 100644 lib/common/models/wallpaper_model.dart create mode 100644 lib/common/models/wallpaper_model.g.dart create mode 100644 lib/common/network/base_error.dart create mode 100644 lib/common/network/dio_client.dart create mode 100644 lib/common/network/dio_interceptor.dart create mode 100644 lib/common/storage/favorite_data.dart create mode 100644 lib/common/storage/hive_storage.dart delete mode 100644 lib/common/theme/colors.dart delete mode 100644 lib/common/theme/sizes.dart delete mode 100644 lib/common/theme/themes.dart create mode 100644 lib/common/utils/device_info_util.dart create mode 100644 lib/common/utils/download_util.dart create mode 100644 lib/common/utils/filesize_util.dart create mode 100644 lib/common/utils/local_path_util.dart rename lib/{ => common}/utils/log_print.dart (95%) create mode 100644 lib/common/utils/num_util.dart rename lib/{ => common}/utils/obj_util.dart (96%) create mode 100644 lib/common/utils/permission_util.dart create mode 100644 lib/generated/assets.dart create mode 100644 lib/generated/json/base/json_convert_content.dart create mode 100644 lib/generated/json/base/json_field.dart create mode 100644 lib/generated/json/base_resp_model.g.dart create mode 100644 lib/generated/json/wallpaper_model.g.dart create mode 100644 lib/modules/about/about_binding.dart create mode 100644 lib/modules/about/about_controller.dart create mode 100644 lib/modules/about/about_view.dart create mode 100644 lib/modules/favorite/favorite_controller.dart create mode 100644 lib/modules/favorite/favorite_view.dart delete mode 100644 lib/modules/home/home_binding.dart create mode 100644 lib/modules/home/home_cls/home_cls_controller.dart create mode 100644 lib/modules/home/home_cls/home_cls_view.dart create mode 100644 lib/modules/me/me_controller.dart create mode 100644 lib/modules/me/me_view.dart create mode 100644 lib/modules/root/root_binding.dart create mode 100644 lib/modules/root/root_controller.dart create mode 100644 lib/modules/root/root_view.dart create mode 100644 lib/modules/wallpaper_det/wallpaper_det_binding.dart create mode 100644 lib/modules/wallpaper_det/wallpaper_det_controller.dart create mode 100644 lib/modules/wallpaper_det/wallpaper_det_view.dart create mode 100644 lib/modules/web_page/web_page_binding.dart create mode 100644 lib/modules/web_page/web_page_controller.dart create mode 100644 lib/modules/web_page/web_page_view.dart create mode 100644 lib/res/themes/app_colors.dart create mode 100644 lib/res/themes/app_sizes.dart rename lib/{common/theme/styles.dart => res/themes/app_styles.dart} (100%) create mode 100644 lib/res/themes/app_themes.dart rename lib/{common/config => res/values}/strings.dart (100%) delete mode 100644 lib/utils/screen_adapter.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index ffccf3c..d898dcb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -70,6 +70,9 @@ android { } release { + ndk{ + abiFilters "armeabi", "armeabi-v7a", "arm64-v8a" + } // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.release diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1bce7cb..7ca3825 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,27 +1,38 @@ - + + + + + + + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:requestLegacyExternalStorage="true"> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> - - + + - - + + diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml index f74085f..4cd4408 100644 --- a/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,12 +1,12 @@ - - + - + + - + android:src="@mipmap/ic_launcher" /> + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 304732f..578f957 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,11 @@ - - + - + - + android:src="@mipmap/ic_launcher" /> + diff --git a/assets/images/error.png b/assets/images/error.png new file mode 100644 index 0000000000000000000000000000000000000000..7ef1e09cd402acb58cdedac6d4b726c2df5bcd12 GIT binary patch literal 2596 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 n3QL(oy91Vz{&I-D;yd6! /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 39CCEDF39CF579238AE308F0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -238,6 +325,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 405E88F0300BCE9602B4DA07 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +357,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + 9F3C7A1490C44408DFBA2D44 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -327,6 +453,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -379,6 +506,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 418D1D078E33D929E8A6B10D /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,6 +524,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1561731E247DBD5F8A0E0B0F /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,6 +540,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 7B71BC8DD4AFE8E93734BDDC /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -448,6 +578,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -505,6 +636,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..e9c9b88 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,14 @@ { "images" : [ { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } } diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 7353c41ecf9ca08017312dc233d9830079b50717..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 295 zcmV+?0oeYDP)xN#0001NP)t-s|Ns9~ z#rXRE|M&d=0au&!`~QyF`q}dRnBDt}*!qXo`c{v z{Djr|@Adh0(D_%#_&mM$D6{kE_x{oE{l@J5@%H*?%=t~i_`ufYOPkAEn!pfkr2$fs z652Tz0001XNklqeeKN4RM4i{jKqmiC$?+xN>3Apn^ z0QfuZLym_5b<*QdmkHjHlj811{If)dl(Z2K0A+ekGtrFJb?g|wt#k#pV-#A~bK=OT ts8>{%cPtyC${m|1#B1A6#u!Q;umknL1chzTM$P~L002ovPDHLkV1lTfnu!1a diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 797d452e458972bab9d994556c8305db4c827017..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 6ed2d933e1120817fe9182483a228007b18ab6ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cd7b0099ca80c806f8fe495613e8d6c69460d76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe730945a01f64a61e2235dbe3f45b08f7729182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 502f463a9bc882b461c96aadf492d1729e49e725..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 0ec303439225b78712f49115768196d8d76f6790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index e9f5fea27c705180eb716271f41b582e76dcbd90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1674 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json index 0bedcf2..91348fa 100644 --- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -1,23 +1,21 @@ { "images" : [ { + "filename" : "Icon-App-1024x1024@1x.png", "idiom" : "universal", - "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } } diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eacad3b03bb08bbddbbf4ac48dd78b3d838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725..0000000 --- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard index f2e259c..1d83ea2 100644 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -14,9 +16,15 @@ + - + + + + + + @@ -28,10 +36,10 @@ - + - + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index f3c2851..0659f5b 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -14,13 +16,14 @@ - + - + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 1ef34f9..67054b0 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,7 @@ UIApplicationSupportsIndirectInputEvents + NSPhotoLibraryAddUsageDescription + Please allow the APP to save photos to the album diff --git a/lib/common/components/base_appbar.dart b/lib/common/components/base_appbar.dart new file mode 100644 index 0000000..22417f2 --- /dev/null +++ b/lib/common/components/base_appbar.dart @@ -0,0 +1,70 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: BaseAppBar + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; + +class BaseAppBar extends StatelessWidget implements PreferredSizeWidget { + const BaseAppBar( + this.titleStr, { + super.key, + this.height, + this.backgroundColor, + this.titleStyle, + this.title, + this.iconColor, + this.showLeading = true, + this.leading, + this.leadingOnPressed, + this.actions, + this.systemOverlayStyle, + this.bottom, + }); + + final double? height; + final Color? backgroundColor; + final String titleStr; + final TextStyle? titleStyle; + final Widget? title; + final Color? iconColor; + final bool showLeading; + final Widget? leading; + final Function()? leadingOnPressed; + final List? actions; + final SystemUiOverlayStyle? systemOverlayStyle; + final PreferredSizeWidget? bottom; + + @override + Widget build(BuildContext context) { + Widget titleWidget = title ?? + Text( + titleStr, + style: titleStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + Widget? leadingWidget = showLeading + ? (leading ?? + IconButton( + icon: Icon( + Icons.arrow_back_ios_rounded, + color: iconColor, + ), + onPressed: leadingOnPressed ?? Get.back, + )) + : null; + return AppBar( + title: titleWidget, + backgroundColor: backgroundColor, + systemOverlayStyle: systemOverlayStyle, + leading: leadingWidget, + actions: actions, + bottom: bottom, + ); + } + + @override + Size get preferredSize => Size.fromHeight(height ?? kToolbarHeight); +} diff --git a/lib/common/components/base_masonry_gridview.dart b/lib/common/components/base_masonry_gridview.dart new file mode 100644 index 0000000..09be9da --- /dev/null +++ b/lib/common/components/base_masonry_gridview.dart @@ -0,0 +1,56 @@ +// Author: fengshengxiong +// Date: 2024/5/11 +// Description: 瀑布流 + +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:wallpaper/common/components/image_widget.dart'; +import 'package:wallpaper/common/components/view_state_widget.dart'; +import 'package:wallpaper/common/models/wallpaper_model.dart'; + +class BaseMasonryGridView extends StatelessWidget { + const BaseMasonryGridView({ + super.key, + this.viewState = ViewState.normal, + required this.wallpaperList, + required this.itemOnTap, + this.onLongPress, + }); + + final List wallpaperList; + final Function(int index) itemOnTap; + final Function(int index)? onLongPress; + final ViewState? viewState; + + @override + Widget build(BuildContext context) { + return ViewStateWidget( + state: viewState!, + child: MasonryGridView.count( + itemCount: wallpaperList.length, + crossAxisCount: 2, + mainAxisSpacing: 8.w, + crossAxisSpacing: 8.w, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 8).w, + itemBuilder: (context, index) { + return _wallpaperItem(wallpaperList[index], index); + }, + ), + ); + } + + Widget _wallpaperItem(WallpaperData item, index) { + return InkWell( + onTap: () => itemOnTap(index), + borderRadius: BorderRadius.circular(8).r, + onLongPress: onLongPress != null ? () => onLongPress!(index) : null, + child: ImageWidget( + url: item.previewThumb, + fit: BoxFit.contain, + radius: 8.r, + ), + ); + } +} diff --git a/lib/common/components/dialog/hint_dialog.dart b/lib/common/components/dialog/hint_dialog.dart new file mode 100644 index 0000000..5f28fd7 --- /dev/null +++ b/lib/common/components/dialog/hint_dialog.dart @@ -0,0 +1,117 @@ +// Author: fengshengxiong +// Date: 2024/5/11 +// Description: 提示框 + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/divider_widget.dart'; + +class HintDialog extends StatelessWidget { + const HintDialog({ + super.key, + this.title, + this.content, + this.showCancelBtn = true, + this.cancelText, + this.confirmText, + this.confirmOnTap, + }); + + final String? title; + final String? content; + final bool? showCancelBtn; + final String? cancelText; + final String? confirmText; + final Function()? confirmOnTap; + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + child: Center( + child: Container( + width: 0.8.sw, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8).r, + ), + child: IntrinsicHeight( + child: Column( + children: [ + SizedBox(height: 10.h), + Text( + title ?? 'Reminder', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black, + fontSize: 18.sp, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 10.h), + Text( + content ?? '', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black54, + fontSize: 15.sp, + ), + ), + SizedBox(height: 10.h), + const DividerWidget(), + SizedBox( + height: 48.h, + child: Row( + children: [ + if (showCancelBtn!) ...[ + _optionButton(cancelText ?? 'Cancel', false), + Container( + width: 1.w, + height: double.infinity, + color: const Color(0xFFE5E5E5), + ), + ], + _optionButton(confirmText ?? 'Confirm', true), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _optionButton(String label, bool isConfirm) { + return Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + Get.back(); + if (isConfirm && confirmOnTap != null) { + confirmOnTap!(); + } + }, + child: SizedBox( + height: double.infinity, + child: Center( + child: Text( + label, + textAlign: TextAlign.center, + style: TextStyle( + color: isConfirm ? Colors.black : Colors.black45, + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/common/components/dialog/process_dialog.dart b/lib/common/components/dialog/process_dialog.dart new file mode 100644 index 0000000..594fe22 --- /dev/null +++ b/lib/common/components/dialog/process_dialog.dart @@ -0,0 +1,94 @@ +// Author: fengshengxiong +// Date: 2024/5/9 +// Description: 进度框 + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:wallpaper/common/components/view_state_widget.dart'; +import 'package:wallpaper/common/utils/obj_util.dart'; + +class ProcessDialog extends StatelessWidget { + final double process; + final String processStr; + final Function() cancelOnTap; + final bool showCanCancel; + + const ProcessDialog( + this.process, + this.processStr, { + super.key, + this.showCanCancel = true, + required this.cancelOnTap, + }); + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + child: Center( + child: IntrinsicWidth( + child: Container( + constraints: BoxConstraints( + minWidth: 80.w, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8).r, + ), + child: IntrinsicHeight( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 10.h), + loadingView( + valueColor: const AlwaysStoppedAnimation(Colors.black), + value: process, + ), + if (ObjUtil.isNotEmptyStr(processStr)) ...[ + SizedBox(height: 10.h), + Expanded( + child: Text( + processStr, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.sp, + color: Colors.black, + ), + ), + ), + ], + if (!showCanCancel) ...[ + SizedBox(height: 10.h), + ], + if (showCanCancel) ...[ + SizedBox( + height: 40.h, + child: TextButton( + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + backgroundColor: Colors.transparent, + ), + onPressed: cancelOnTap, + child: Text( + 'Cancel', + style: TextStyle( + color: Colors.black, + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/common/components/divider_widget.dart b/lib/common/components/divider_widget.dart new file mode 100644 index 0000000..49c96f4 --- /dev/null +++ b/lib/common/components/divider_widget.dart @@ -0,0 +1,22 @@ +// Author: fengshengxiong +// Date: 2024/5/9 +// Description: 分割线 + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class DividerWidget extends StatelessWidget { + const DividerWidget({super.key, this.height, this.color}); + + final double? height; + final Color? color; + + @override + Widget build(BuildContext context) { + return Divider( + height: height ?? 1.w, + thickness: height ?? 1.w, + color: color ?? const Color(0xFFE5E5E5), + ); + } +} diff --git a/lib/common/components/easy_loading.dart b/lib/common/components/easy_loading.dart index 3875b76..bf7ae80 100644 --- a/lib/common/components/easy_loading.dart +++ b/lib/common/components/easy_loading.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import '../theme/colors.dart'; +import 'package:wallpaper/common/utils/obj_util.dart'; /// EasyLoading配置 void configLoading() { @@ -26,7 +26,7 @@ void configLoading() { // 指示器的大小, 默认40.0. ..indicatorSize = 26.0 // loading的圆角大小, 默认5.0. - ..radius = 5.0 + ..radius = 8.0 // 文本大小, 默认15.0. ..fontSize = 14.0 // 进度条指示器的宽度, 默认2.0. @@ -34,39 +34,39 @@ void configLoading() { // 指示器的宽度, 默认4.0, 仅对[EasyLoadingIndicatorType.ring, EasyLoadingIndicatorType.dualRing]有效. ..lineWidth = 2.0 // [showSuccess] [showError] [showInfo]的展示时间, 默认2000ms. - ..displayDuration = const Duration(milliseconds: 1000) + ..displayDuration = const Duration(milliseconds: 2000) // 动画时间, 默认200ms. ..animationDuration = const Duration(milliseconds: 200) // 文本的颜色, 仅对[EasyLoadingStyle.custom]有效. - ..textColor = white + ..textColor = Colors.black // 指示器的颜色, 仅对[EasyLoadingStyle.custom]有效. - ..indicatorColor = white + ..indicatorColor = Colors.black // 进度条指示器的颜色, 仅对[EasyLoadingStyle.custom]有效. - ..progressColor = white + ..progressColor = Colors.black // loading的背景色, 仅对[EasyLoadingStyle.custom]有效. - ..backgroundColor = loadingBg + ..backgroundColor = Colors.white // 遮罩的背景色, 仅对[EasyLoadingMaskType.custom]有效. - ..maskColor = black.withOpacity(0.3) + ..maskColor = Colors.black.withOpacity(0.3) // 当loading展示的时候,是否允许用户操作. ..userInteractions = false // 点击背景是否关闭. ..dismissOnTap = false; } -void toast(String? value, {bool isShow = true}) { - if (isShow) { - EasyLoading.showToast(value ?? ''); +void toast(String? value, {bool show = true}) { + if (show && ObjUtil.isNotEmptyStr(value)) { + EasyLoading.showToast('$value'); } } -void loading({String? value, bool isShow = true}) { - if (isShow) { +void loading({String? value, bool show = true}) { + if (show) { EasyLoading.show(status: value); } } -void dismiss({bool isDismiss = true}) { - if (isDismiss) { +void dismiss({bool dismiss = true}) { + if (dismiss) { EasyLoading.dismiss(); } } diff --git a/lib/common/components/get_bind_widget.dart b/lib/common/components/get_bind_widget.dart new file mode 100644 index 0000000..9267301 --- /dev/null +++ b/lib/common/components/get_bind_widget.dart @@ -0,0 +1,95 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 该组件可以回收GetXController,用于处理无法某些组件的GetXController无法回收的情况 + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +/// GetBindWidget can bind GetxController, and when the page is disposed, +/// it can automatically destroy the bound related GetXController +/// +/// +/// Sample: +/// +/// class SampleController extends GetxController { +/// final String title = 'My Awesome View'; +/// } +/// +/// class SamplePage extends StatelessWidget { +/// final controller = SampleController(); +/// +/// @override +/// Widget build(BuildContext context) { +/// return GetBindWidget( +/// bind: controller, +/// child: Container(), +/// ); +/// } +/// } +class GetBindWidget extends StatefulWidget { + const GetBindWidget({ + super.key, + this.bind, + this.tag, + this.binds, + this.tags, + required this.child, + }) : assert( + binds == null || tags == null || binds.length == tags.length, + 'The binds and tags arrays length should be equal\n' + 'and the elements in the two arrays correspond one-to-one', + ); + + final GetxController? bind; + final String? tag; + + final List? binds; + final List? tags; + + final Widget child; + + @override + GetBindWidgetState createState() => GetBindWidgetState(); +} + +class GetBindWidgetState extends State { + @override + Widget build(BuildContext context) { + return widget.child; + } + + @override + void dispose() { + _closeGetXController(); + _closeGetXControllers(); + super.dispose(); + } + + ///Close GetxController bound to the current page + void _closeGetXController() { + if (widget.bind == null) { + return; + } + + var key = widget.bind.runtimeType.toString() + (widget.tag ?? ''); + GetInstance().delete(key: key); + } + + ///Batch close GetxController bound to the current page + void _closeGetXControllers() { + if (widget.binds == null) { + return; + } + + for (var i = 0; i < widget.binds!.length; i++) { + var type = widget.binds![i].runtimeType.toString(); + + if (widget.tags == null) { + GetInstance().delete(key: type); + } else { + var key = type + (widget.tags?[i] ?? ''); + GetInstance().delete(key: key); + } + } + } +} diff --git a/lib/common/components/image_widget.dart b/lib/common/components/image_widget.dart new file mode 100644 index 0000000..ec5e272 --- /dev/null +++ b/lib/common/components/image_widget.dart @@ -0,0 +1,71 @@ +// Author: fengshengxiong +// Date: 2024/5/9 +// Description: 加载网络图像 + +import 'dart:io'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:wallpaper/generated/assets.dart'; + +class ImageWidget extends StatelessWidget { + const ImageWidget({ + super.key, + this.width, + this.height, + this.radius = 0.0, + this.url, + this.filePath, + this.isLocal = false, + this.fit = BoxFit.cover, + this.placeholder, + this.errorWidget, + }); + + final double? width, height; + final double radius; + final String? url; + final String? filePath; + final bool isLocal; + final BoxFit fit; + final Widget? placeholder; + final Widget? errorWidget; + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(radius), + child: Visibility( + visible: !isLocal, + replacement: Image.file( + File('$filePath'), + fit: fit, + width: width, + height: height, + ), + child: CachedNetworkImage( + width: width, + height: height, + imageUrl: '$url', + fit: fit, + placeholder: (context, url) { + return placeholder ?? _placeErrWidget(Assets.imagesPlaceholder); + }, + errorWidget: (context, url, error) { + return errorWidget ?? _placeErrWidget(Assets.imagesError); + }, + ), + ), + ); + } + + Widget _placeErrWidget(String imgName) { + return Container( + color: Colors.white10, + child: Image.asset( + imgName, + color: Colors.white10, + ), + ); + } +} diff --git a/lib/common/components/keep_alive_wrapper.dart b/lib/common/components/keep_alive_wrapper.dart new file mode 100644 index 0000000..f97428e --- /dev/null +++ b/lib/common/components/keep_alive_wrapper.dart @@ -0,0 +1,30 @@ +// Author: fengshengxiong +// Date: 2024/5/9 +// Description: 保持组件状态 + +import 'package:flutter/material.dart'; + +class KeepAliveWrapper extends StatefulWidget { + const KeepAliveWrapper({ + super.key, + required this.child, + this.keepAlive = true, + }); + + final Widget child; + final bool keepAlive; + + @override + State createState() => _KeepAliveWrapperState(); +} + +class _KeepAliveWrapperState extends State with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return widget.child; + } + + @override + bool get wantKeepAlive => widget.keepAlive; +} diff --git a/lib/common/components/title_bar_widget.dart b/lib/common/components/title_bar_widget.dart new file mode 100644 index 0000000..72cb9d1 --- /dev/null +++ b/lib/common/components/title_bar_widget.dart @@ -0,0 +1,69 @@ +// Author: fengshengxiong +// Date: 2024/5/11 +// Description: 标题栏 + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:wallpaper/res/themes/app_sizes.dart'; + +class TitleBarWidget extends StatelessWidget { + const TitleBarWidget({ + super.key, + required this.title, + this.showMenuBtn = false, + this.deleteOnTap, + }); + + final String title; + final bool? showMenuBtn; + final Function()? deleteOnTap; + + @override + Widget build(BuildContext context) { + return Container( + height: kTextTabBarHeight, + alignment: Alignment.centerLeft, + child: Row( + children: [ + SizedBox(width: 16.w), + Expanded( + child: Text( + title, + style: TextStyle( + color: Colors.white, + fontSize: 22.sp, + fontWeight: FontWeight.bold, + ), + ), + ), + if (showMenuBtn!) ...[ + IconButton( + onPressed: () { + _showPopMenu(context); + }, + icon: const Icon(Icons.menu, color: Colors.white), + ), + ], + ], + ), + ); + } + + void _showPopMenu(context) { + showMenu( + context: context, + surfaceTintColor: Colors.white, + position: RelativeRect.fromLTRB(double.infinity, statusToolBarHeight, 0.0, 0.0), + items: [ + PopupMenuItem( + value: 'delete', + onTap: deleteOnTap, + child: const ListTile( + leading: Icon(Icons.delete), + title: Text('Delete All'), + ), + ), + ], + ); + } +} diff --git a/lib/common/components/view_state_widget.dart b/lib/common/components/view_state_widget.dart new file mode 100644 index 0000000..35faf9f --- /dev/null +++ b/lib/common/components/view_state_widget.dart @@ -0,0 +1,75 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 状态视图 + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +/// 四种视图状态 +enum ViewState { normal, error, loading, empty } + +class ViewStateWidget extends StatelessWidget { + const ViewStateWidget({super.key, required this.state, required this.child}); + + final ViewState state; + final Widget child; + + @override + Widget build(BuildContext context) { + switch (state) { + case ViewState.normal: + return child; + case ViewState.loading: + return loadingView(); + case ViewState.error: + return errorView; + case ViewState.empty: + return emptyView; + } + } +} + +/// 加载中视图 +Widget loadingView({ + Color? color, + Animation? valueColor, + Color? backgroundColor, + double? value, +}) { + return Center( + child: CircularProgressIndicator( + color: color, + valueColor: valueColor, + backgroundColor: backgroundColor ?? Colors.grey, + value: value, + ), + ); +} + +/// 空视图 +Widget get emptyView { + return Center( + child: Text( + 'No data available', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + ), + ); +} + +/// 错误视图 +Widget get errorView { + return Center( + child: Text( + 'An error occurred, please try again later', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + ), + ); +} diff --git a/lib/common/components/wallpaper_picker.dart b/lib/common/components/wallpaper_picker.dart new file mode 100644 index 0000000..1206ec2 --- /dev/null +++ b/lib/common/components/wallpaper_picker.dart @@ -0,0 +1,80 @@ +// Author: fengshengxiong +// Date: 2024/5/11 +// Description: 设置锁屏/首页壁纸 + +import 'package:async_wallpaper/async_wallpaper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/divider_widget.dart'; +import 'package:wallpaper/common/components/easy_loading.dart'; +import 'package:wallpaper/res/themes/app_colors.dart'; + +class WallpaperPicker { + static void picker(String imgPath) { + Get.bottomSheet( + Container( + color: Colors.white, + child: IntrinsicHeight( + child: Column( + children: [ + _bottomSheetItem('Lock Screen', () { + Get.back(); + setWallpaper(imgPath, AsyncWallpaper.LOCK_SCREEN); + }), + const DividerWidget(), + _bottomSheetItem('Home Screen', () { + Get.back(); + setWallpaper(imgPath, AsyncWallpaper.HOME_SCREEN); + }), + const DividerWidget(), + _bottomSheetItem('Both Screen', () { + Get.back(); + setWallpaper(imgPath, AsyncWallpaper.BOTH_SCREENS); + }), + const DividerWidget(), + _bottomSheetItem('Cancel', Get.back), + ], + ), + ), + ), + ); + } + + static Widget _bottomSheetItem(String label, Function() onTap) { + return SizedBox( + width: 1.sw, + child: TextButton( + onPressed: onTap, + child: Text( + label, + style: TextStyle( + color: seedColor, + fontSize: 18.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + } + + static Future setWallpaper(String imgPath, int wallpaperLocation) async { + loading(); + String result; + try { + result = await AsyncWallpaper.setWallpaperFromFile( + filePath: imgPath, + wallpaperLocation: AsyncWallpaper.LOCK_SCREEN, + // toastDetails: ToastDetails.success(), + // errorToastDetails: ToastDetails.error(), + ) + ? 'Wallpaper applied successfully' + : 'Failed to get wallpaper'; + } on PlatformException { + result = 'Failed to get wallpaper'; + } + dismiss(); + toast(result); + } +} diff --git a/lib/common/data/api/api.dart b/lib/common/data/api/api.dart deleted file mode 100644 index 28ad693..0000000 --- a/lib/common/data/api/api.dart +++ /dev/null @@ -1,8 +0,0 @@ -// Author: fengshengxiong -// Date: 2024/5/7 -// Description: 接口Api - -class Api { - /// baseUrl - static const String baseUrl = 'http://47.108.227.40:6376/'; -} diff --git a/lib/common/models/base_resp_model.dart b/lib/common/models/base_resp_model.dart new file mode 100644 index 0000000..d0dcac0 --- /dev/null +++ b/lib/common/models/base_resp_model.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; + +import 'package:wallpaper/generated/json/base/json_field.dart'; +import 'package:wallpaper/generated/json/base_resp_model.g.dart'; + +@JsonSerializable() +class BaseRespModel { + int? code; + String? message; + dynamic data; + + BaseRespModel(); + + factory BaseRespModel.fromJson(Map json) => $BaseRespModelFromJson(json); + + Map toJson() => $BaseRespModelToJson(this); + + @override + String toString() { + return jsonEncode(this); + } +} \ No newline at end of file diff --git a/lib/common/models/wallpaper_model.dart b/lib/common/models/wallpaper_model.dart new file mode 100644 index 0000000..9ff01b5 --- /dev/null +++ b/lib/common/models/wallpaper_model.dart @@ -0,0 +1,50 @@ +import 'dart:convert'; +import 'package:hive/hive.dart'; +import 'package:wallpaper/generated/json/base/json_field.dart'; +import 'package:wallpaper/generated/json/wallpaper_model.g.dart'; + +part 'wallpaper_model.g.dart'; + +@JsonSerializable() +class WallpaperModel { + List? data; + String? name; + + WallpaperModel(); + + factory WallpaperModel.fromJson(Map json) => $WallpaperModelFromJson(json); + + Map toJson() => $WallpaperModelToJson(this); + + @override + String toString() { + return jsonEncode(this); + } +} + +@HiveType(typeId: 1) +@JsonSerializable() +class WallpaperData extends HiveObject{ + @HiveField(0) + String? banner; + + @HiveField(1) + String? original; + + @HiveField(2) + String? previewThumb; + + @HiveField(3) + String? source; + + WallpaperData(); + + factory WallpaperData.fromJson(Map json) => $WallpaperDataFromJson(json); + + Map toJson() => $WallpaperDataToJson(this); + + @override + String toString() { + return jsonEncode(this); + } +} \ No newline at end of file diff --git a/lib/common/models/wallpaper_model.g.dart b/lib/common/models/wallpaper_model.g.dart new file mode 100644 index 0000000..d6bde58 --- /dev/null +++ b/lib/common/models/wallpaper_model.g.dart @@ -0,0 +1,49 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'wallpaper_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class WallpaperDataAdapter extends TypeAdapter { + @override + final int typeId = 1; + + @override + WallpaperData read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return WallpaperData() + ..banner = fields[0] as String? + ..original = fields[1] as String? + ..previewThumb = fields[2] as String? + ..source = fields[3] as String?; + } + + @override + void write(BinaryWriter writer, WallpaperData obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.banner) + ..writeByte(1) + ..write(obj.original) + ..writeByte(2) + ..write(obj.previewThumb) + ..writeByte(3) + ..write(obj.source); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is WallpaperDataAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/common/network/base_error.dart b/lib/common/network/base_error.dart new file mode 100644 index 0000000..f9059f4 --- /dev/null +++ b/lib/common/network/base_error.dart @@ -0,0 +1,39 @@ +// Author: fengshengxiong +// Date: 2024/5/9 +// Description: 服务端基本错误类型封装类 + +abstract class BaseError { + final int? code; + final String? message; + + BaseError({this.code, this.message}); +} + +class NeedLogin implements BaseError { + @override + int get code => 401; + + @override + String get message => "Unauthenticated"; +} + +class NeedAuth implements BaseError { + @override + int get code => 403; + + @override + String get message => "Unauthorized access"; +} + +class OtherError implements BaseError { + final int? statusCode; + final String? statusMessage; + + OtherError({this.statusCode, this.statusMessage}); + + @override + int? get code => statusCode; + + @override + String? get message => statusMessage; +} diff --git a/lib/common/network/dio_client.dart b/lib/common/network/dio_client.dart new file mode 100644 index 0000000..f83f94e --- /dev/null +++ b/lib/common/network/dio_client.dart @@ -0,0 +1,169 @@ +// Author: fengshengxiong +// Date: 2024/5/9 +// Description: dio封装 + +import 'package:dio/dio.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; +import 'package:wallpaper/common/components/easy_loading.dart'; +import 'package:wallpaper/common/models/base_resp_model.dart'; +import 'package:wallpaper/common/network/base_error.dart'; +import 'package:wallpaper/common/network/dio_interceptor.dart'; +import 'package:wallpaper/common/utils/log_print.dart'; +import 'package:wallpaper/generated/json/base/json_convert_content.dart'; + +class DioClient { + static final DioClient _instance = DioClient._internal(); + + factory DioClient() => _instance; + late Dio _dio; + + DioClient._internal() { + _dio = Dio(); + final baseOptions = BaseOptions( + baseUrl: '', + connectTimeout: const Duration(seconds: 15), + receiveTimeout: const Duration(seconds: 10), + ); + _dio.options = baseOptions; + _dio.interceptors.add(DioInterceptor()); + _dio.interceptors.add(PrettyDioLogger( + requestHeader: true, + requestBody: true, + compact: false, + )); + } + + /// 请求 + Future request( + String path, { + required RequestMethod requestMethod, + dynamic data, + Map? queryParameters, + CancelToken? cancelToken, + Options? options, + bool isFormData = false, + bool showLoading = false, + bool showToast = true, + required Function(T? result) success, + Function(BaseError baseError)? fail, + }) async { + try { + loading(show: showLoading); + Response response = await _dio.request( + path, + data: isFormData ? FormData.fromMap(data) : data, + queryParameters: queryParameters, + cancelToken: cancelToken, + options: _getOptions(requestMethod, options), + ); + dismiss(dismiss: showLoading); + BaseRespModel baseRespModel = response.data; + if (baseRespModel.code == 200) { + success(JsonConvert.fromJsonAsT(baseRespModel.data)); + } else { + toast(baseRespModel.message, show: showToast); + if (fail != null) { + fail(OtherError( + statusCode: baseRespModel.code, + statusMessage: baseRespModel.message, + )); + } + } + } on DioException catch (e) { + BaseError error = getError(e); + toast(error.message, show: showToast); + if (fail != null) fail(error); + } catch (e) { + LogPrint.e(e.toString()); + } + } + + Options? _getOptions(RequestMethod requestMethod, Options? options) { + options ??= Options(); + options.method = requestMethod.method; + return options; + } + + /// 下载 + Future download( + String urlPath, + dynamic savePath, { + ProgressCallback? onReceiveProgress, + Map? queryParameters, + CancelToken? cancelToken, + data, + Options? options, + required Function() success, + Function(String? err)? fail, + }) async { + try { + await _dio.download( + urlPath, + savePath, + onReceiveProgress: onReceiveProgress, + queryParameters: queryParameters, + cancelToken: cancelToken, + data: data, + options: Options(receiveTimeout: const Duration(seconds: 0)), + ); + success(); + } on DioException catch (e) { + BaseError error = getError(e); + toast(error.message); + if (fail != null) fail(error.message); + } catch (e) { + LogPrint.e(e.toString()); + toast(e.toString()); + if (fail != null) fail(e.toString()); + } + } + + BaseError getError(DioException e) { + if (e.runtimeType == DioException) { + switch (e.type) { + case DioExceptionType.connectionTimeout: + return OtherError(statusCode: -1, statusMessage: 'connection timed out'); + case DioExceptionType.sendTimeout: + return OtherError(statusCode: -1, statusMessage: 'send timeout'); + case DioExceptionType.receiveTimeout: + return OtherError(statusCode: -1, statusMessage: 'receive timeout'); + case DioExceptionType.badCertificate: + return OtherError(statusCode: -1, statusMessage: 'certificate error'); + case DioExceptionType.cancel: + return OtherError(statusCode: -1, statusMessage: 'request canceled'); + case DioExceptionType.connectionError: + return OtherError(statusCode: -1, statusMessage: 'connection error'); + case DioExceptionType.unknown: + return OtherError(statusCode: -1, statusMessage: 'unknown error'); + case DioExceptionType.badResponse: + final response = e.response; + if (response!.statusCode == 401) { + return NeedLogin(); + } else if (response.statusCode == 403) { + return NeedAuth(); + } else { + return OtherError( + statusCode: response.statusCode, + statusMessage: response.statusMessage, + ); + } + } + } + return OtherError(statusCode: -1, statusMessage: 'unknown error'); + } +} + +enum RequestMethod { + get('GET'), + post('POST'), + put('PUT'), + head('HEAD'), + delete('DELETE'), + patch('PATCH'); + + const RequestMethod( + this.method, + ); + + final String method; +} diff --git a/lib/common/network/dio_interceptor.dart b/lib/common/network/dio_interceptor.dart new file mode 100644 index 0000000..c86acf0 --- /dev/null +++ b/lib/common/network/dio_interceptor.dart @@ -0,0 +1,31 @@ +// Author: fengshengxiong +// Date: 2024/5/9 +// Description: dio拦截器 + +import 'dart:convert'; +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:wallpaper/common/models/base_resp_model.dart'; + +class DioInterceptor extends Interceptor { + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + if (response.data is! ResponseBody) { + if (response.statusCode == HttpStatus.ok) { + BaseRespModel baseRespModel = BaseRespModel(); + if (response.data != null && response.data is Map) { + baseRespModel = BaseRespModel.fromJson(response.data); + } else { + try { + baseRespModel = BaseRespModel.fromJson(jsonDecode(response.data)); + } catch (e) { + baseRespModel.data = response.data; + } + } + response.data = baseRespModel; + return handler.resolve(response); + } + } + super.onResponse(response, handler); + } +} diff --git a/lib/common/storage/favorite_data.dart b/lib/common/storage/favorite_data.dart new file mode 100644 index 0000000..c067aca --- /dev/null +++ b/lib/common/storage/favorite_data.dart @@ -0,0 +1,44 @@ +// Author: fengshengxiong +// Date: 2024/5/8 +// Description: 收藏数据 + +import 'package:wallpaper/common/models/wallpaper_model.dart'; + +import 'hive_storage.dart'; + +class FavoriteData { + /// 私有构造函数 + FavoriteData._(); + + /// 静态常量用于保存类的唯一实例 + static final FavoriteData _instance = FavoriteData._(); + + /// 工厂构造函数返回类的唯一实例 + factory FavoriteData() { + return _instance; + } + + /// 声明盒子 + /// 注意, main函数中这个盒子已经打开, 可以进行存储操作 + final _box = getFavoriteBox(); + + /// 获取壁纸 + List getWallpaperData() { + return _box.values.toList(); + } + + /// 存储壁纸 + void setWallpaperData(WallpaperData wallpaperData) { + _box.add(wallpaperData); + } + + /// 删除壁纸 + void delete(index) { + _box.deleteAt(index); + } + + /// 删除所有壁纸 + void clear() { + _box.clear(); + } +} diff --git a/lib/common/storage/hive_storage.dart b/lib/common/storage/hive_storage.dart new file mode 100644 index 0000000..997f202 --- /dev/null +++ b/lib/common/storage/hive_storage.dart @@ -0,0 +1,22 @@ +// Author: fengshengxiong +// Date: 2024/5/8 +// Description: 持久化储存 + +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:wallpaper/common/models/wallpaper_model.dart'; + +const favoriteBox = 'favoriteBox'; + +Future initHive() async { + // 初始化 + await Hive.initFlutter(); + // 注册类型适配器 + Hive.registerAdapter(WallpaperDataAdapter()); + // 打开盒子 + await Hive.openBox(favoriteBox); +} + +/// 获取盒子 +Box getFavoriteBox() { + return Hive.box(favoriteBox); +} \ No newline at end of file diff --git a/lib/common/theme/colors.dart b/lib/common/theme/colors.dart deleted file mode 100644 index 36a707e..0000000 --- a/lib/common/theme/colors.dart +++ /dev/null @@ -1,16 +0,0 @@ -// Author: fengshengxiong -// Date: 2024/5/7 -// Description: 颜色 - -import 'package:flutter/material.dart'; - -/// 主要颜色 -const primary = Colors.deepPurple; - -const white = Colors.white; - -const black = Colors.black; - -const grey = Colors.grey; - -const loadingBg = Color(0xFF616161); \ No newline at end of file diff --git a/lib/common/theme/sizes.dart b/lib/common/theme/sizes.dart deleted file mode 100644 index c019654..0000000 --- a/lib/common/theme/sizes.dart +++ /dev/null @@ -1,3 +0,0 @@ -// Author: fengshengxiong -// Date: 2024/5/8 -// Description: 大小 \ No newline at end of file diff --git a/lib/common/theme/themes.dart b/lib/common/theme/themes.dart deleted file mode 100644 index 68825a0..0000000 --- a/lib/common/theme/themes.dart +++ /dev/null @@ -1,33 +0,0 @@ -// Author: fengshengxiong -// Date: 2024/5/7 -// Description: 主题 - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'colors.dart'; - -/// 浅色主题 -ThemeData lightTheme = ThemeData.light(useMaterial3: true).copyWith( - colorScheme: ColorScheme.fromSeed(seedColor: primary), - primaryColor: primary, - scaffoldBackgroundColor: white, - appBarTheme: AppBarTheme( - systemOverlayStyle: SystemUiOverlayStyle.light, - backgroundColor: primary, - iconTheme: const IconThemeData(color: white), - elevation: 0.0, - centerTitle: true, - titleTextStyle: TextStyle(color: white, fontSize: 25.sp, fontWeight: FontWeight.w500), - ), - textSelectionTheme: const TextSelectionThemeData( - cursorColor: primary, - selectionHandleColor: primary, - ), -); - -/// 深色主题 -ThemeData darkTheme = ThemeData.dark(useMaterial3: true).copyWith( - colorScheme: ColorScheme.fromSeed(seedColor: primary), - primaryColor: primary, -); \ No newline at end of file diff --git a/lib/common/utils/device_info_util.dart b/lib/common/utils/device_info_util.dart new file mode 100644 index 0000000..a66f215 --- /dev/null +++ b/lib/common/utils/device_info_util.dart @@ -0,0 +1,15 @@ +// Author: fengshengxiong +// Date: 2024/5/11 +// Description: 设备信息 + +import 'package:device_info_plus/device_info_plus.dart'; + +class DeviceInfoUtil { + /// 获取当前设备的sdk版本 + static Future getSDKVersion() async { + DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; + int sdkVersion = androidInfo.version.sdkInt; + return sdkVersion; + } +} \ No newline at end of file diff --git a/lib/common/utils/download_util.dart b/lib/common/utils/download_util.dart new file mode 100644 index 0000000..6d3216e --- /dev/null +++ b/lib/common/utils/download_util.dart @@ -0,0 +1,78 @@ +// Author: fengshengxiong +// Date: 2024/5/10 +// Description: 下载文件 + +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/view_state_widget.dart'; +import 'package:wallpaper/common/network/dio_client.dart'; +import 'package:wallpaper/common/utils/filesize_util.dart'; +import 'package:wallpaper/common/utils/local_path_util.dart'; +import 'package:wallpaper/common/utils/log_print.dart'; +import 'package:wallpaper/common/utils/num_util.dart'; + +class DownloadUtil { + /// 下载壁纸 + static Future downloadWallpaper(String imageUrl, Function(String savePath) callBack) async { + var process = 0.0.obs; + var processStr = ''.obs; + var cancelToken = CancelToken(); + Get.showSnackbar(GetSnackBar( + borderRadius: 8.r, + margin: const EdgeInsets.symmetric(horizontal: 16).w, + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.white, + titleText: const Text( + 'Loading...', + style: TextStyle(color: Colors.black, fontWeight: FontWeight.w500), + ), + messageText: Obx(() { + return Row( + children: [ + loadingView( + valueColor: const AlwaysStoppedAnimation(Colors.black), + value: process.value, + ), + SizedBox(width: 10.w), + Expanded( + child: Text( + processStr.value, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.sp, + color: Colors.black, + ), + ), + ), + ], + ); + }), + )); + Directory dir = Directory(await LocalPathUtil.getTemporaryPath()); + bool exist = await dir.exists(); + if (!exist) { + // 若目录不存在,先创建 + await dir.create(); + } + // 获取图片文件的名称,使用uri来解析url + Uri uri = Uri.parse(imageUrl); + String savePath = '${dir.path}/${uri.pathSegments.last}'; + LogPrint.d('壁纸保存位置:$savePath'); + await DioClient().download(imageUrl, savePath, cancelToken: cancelToken, onReceiveProgress: (int count, int total) { + if (total != -1) { + process.value = NumUtil.strToDouble(NumUtil.formatNum(count / total)); + processStr.value = '${FileSizeUtil.fileSize(count)}/${FileSizeUtil.fileSize(total)}'; + } + }, success: () { + Get.back(); + callBack(savePath); + }, fail: (err) { + Get.back(); + }); + } +} diff --git a/lib/common/utils/filesize_util.dart b/lib/common/utils/filesize_util.dart new file mode 100644 index 0000000..4b6dcf9 --- /dev/null +++ b/lib/common/utils/filesize_util.dart @@ -0,0 +1,70 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 文件大小单位转换 + +class FileSizeUtil { + /// A method returns a human readable string representing a file fileSize + static String fileSize(dynamic size, [int round = 2]) { + /** + * [size] can be passed as number or as string + * + * the optional parameter [round] specifies the number + * of digits after comma/point (default is 2) + */ + var divider = 1024; + int fileSize; + try { + fileSize = int.parse(size.toString()); + } catch (e) { + throw ArgumentError('Can not parse the size parameter: $e'); + } + + if (fileSize < divider) { + return '$fileSize B'; + } + + if (fileSize < divider * divider && fileSize % divider == 0) { + return '${(fileSize / divider).toStringAsFixed(0)} KB'; + } + + if (fileSize < divider * divider) { + return '${(fileSize / divider).toStringAsFixed(round)} KB'; + } + + if (fileSize < divider * divider * divider && fileSize % divider == 0) { + return '${(fileSize / (divider * divider)).toStringAsFixed(0)} MB'; + } + + if (fileSize < divider * divider * divider) { + return '${(fileSize / divider / divider).toStringAsFixed(round)} MB'; + } + + if (fileSize < divider * divider * divider * divider && fileSize % divider == 0) { + return '${(fileSize / (divider * divider * divider)).toStringAsFixed(0)} GB'; + } + + if (fileSize < divider * divider * divider * divider) { + return '${(fileSize / divider / divider / divider).toStringAsFixed(round)} GB'; + } + + if (fileSize < divider * divider * divider * divider * divider && + fileSize % divider == 0) { + num r = fileSize / divider / divider / divider / divider; + return '${r.toStringAsFixed(0)} TB'; + } + + if (fileSize < divider * divider * divider * divider * divider) { + num r = fileSize / divider / divider / divider / divider; + return '${r.toStringAsFixed(round)} TB'; + } + + if (fileSize < divider * divider * divider * divider * divider * divider && + fileSize % divider == 0) { + num r = fileSize / divider / divider / divider / divider / divider; + return '${r.toStringAsFixed(0)} PB'; + } else { + num r = fileSize / divider / divider / divider / divider / divider; + return '${r.toStringAsFixed(round)} PB'; + } + } +} \ No newline at end of file diff --git a/lib/common/utils/local_path_util.dart b/lib/common/utils/local_path_util.dart new file mode 100644 index 0000000..260f510 --- /dev/null +++ b/lib/common/utils/local_path_util.dart @@ -0,0 +1,28 @@ +// Author: fengshengxiong +// Date: 2024/5/10 +// Description: 本地路径 + +import 'package:path_provider/path_provider.dart'; + +class LocalPathUtil { + /// 获取临时文件路径(IOS和Android通用) + /// Android: /data/user/0/包名/cache + static Future getTemporaryPath() async { + final dir = await getTemporaryDirectory(); + return dir.path; + } + + /// 获取应用支持目录(IOS和Android通用) + /// Android: /data/user/0/包名/files + static Future getSupportPath() async { + final dir = await getApplicationSupportDirectory(); + return dir.path; + } + + /// 获取应用文件目录(IOS和Android通用) + /// Android: /data/user/0/包名/app_flutter + static Future getDocumentsPath() async { + final dir = await getApplicationDocumentsDirectory(); + return dir.path; + } +} \ No newline at end of file diff --git a/lib/utils/log_print.dart b/lib/common/utils/log_print.dart similarity index 95% rename from lib/utils/log_print.dart rename to lib/common/utils/log_print.dart index 47c43f3..3e667e6 100644 --- a/lib/utils/log_print.dart +++ b/lib/common/utils/log_print.dart @@ -3,7 +3,7 @@ // Description: 日志打印 import 'package:logger/logger.dart'; -import '../common/config/strings.dart'; +import 'package:wallpaper/res/values/strings.dart'; final _logger = Logger( printer: PrettyPrinter( diff --git a/lib/common/utils/num_util.dart b/lib/common/utils/num_util.dart new file mode 100644 index 0000000..6741c12 --- /dev/null +++ b/lib/common/utils/num_util.dart @@ -0,0 +1,28 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: NumUtil + +class NumUtil { + static int getInt(num? value) { + if (value == null) return 0; + return value.toInt(); + } + + static double getDouble(num? value) { + if (value == null) return 0.0; + return value.toDouble(); + } + + static String formatNum(double? num, {int index = 2}){ + if (num == null) return '0'; + if((num.toString().length - num.toString().lastIndexOf('.') - 1) < index){ + return num.toStringAsFixed(index).substring(0, num.toString().lastIndexOf('.') + index + 1).toString(); + }else{ + return num.toString().substring(0, num.toString().lastIndexOf('.') + index + 1).toString(); + } + } + + static double strToDouble(String valueStr, {double defValue = 0.0}) { + return double.tryParse(valueStr) ?? defValue; + } +} \ No newline at end of file diff --git a/lib/utils/obj_util.dart b/lib/common/utils/obj_util.dart similarity index 96% rename from lib/utils/obj_util.dart rename to lib/common/utils/obj_util.dart index 23bdfcc..fdd0408 100644 --- a/lib/utils/obj_util.dart +++ b/lib/common/utils/obj_util.dart @@ -3,7 +3,7 @@ // Description: 对象工具类 class ObjUtil { - static bool isNotEmptyString(String? str) { + static bool isNotEmptyStr(String? str) { return str == null || str.trim().isNotEmpty; } diff --git a/lib/common/utils/permission_util.dart b/lib/common/utils/permission_util.dart new file mode 100644 index 0000000..36f2051 --- /dev/null +++ b/lib/common/utils/permission_util.dart @@ -0,0 +1,145 @@ +// Author: fengshengxiong +// Date: 2024/5/10 +// Description: 权限处理 + +import 'package:get/get.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:wallpaper/common/components/dialog/hint_dialog.dart'; + +class PermissionUtil { + /// 检测是否有权限 + /// [permissionList] 权限申请列表 + static Future checkPermission(List permissionList) async { + // 一个新待申请权限列表 + List newPermissionList = []; + // 遍历当前权限申请列表 + for (Permission permission in permissionList) { + PermissionStatus status = await permission.status; + // 如果不是允许状态就添加到新的申请列表中 + if (!status.isGranted) { + newPermissionList.add(permission); + } + } + + // 如果需要重新申请的列表不是空的 + if (newPermissionList.isNotEmpty) { + PermissionStatus permissionStatus = await _requestPermission(newPermissionList); + switch (permissionStatus) { + // 拒绝状态 + case PermissionStatus.denied: + _showFailedDialog(newPermissionList); + return false; + // 允许状态 + case PermissionStatus.granted: + case PermissionStatus.limited: + case PermissionStatus.provisional: + return true; + // 永久拒绝 活动限制 + case PermissionStatus.restricted: + case PermissionStatus.permanentlyDenied: + _showFailedDialog(newPermissionList, isPermanentlyDenied: true); + break; + } + } else { + return true; + } + return false; + } + + /// 获取新列表中的权限 如果有一项不合格就返回false + static Future _requestPermission(List permissionList) async { + Map statuses = await permissionList.request(); + PermissionStatus currentPermissionStatus = PermissionStatus.granted; + statuses.forEach((key, value) { + if (!value.isGranted || !value.isLimited) { + currentPermissionStatus = value; + return; + } + }); + return currentPermissionStatus; + } + + static Future checkLocationAlways() async { + // 获取前置状态 + // Android没有这一步 ios会先访问这个再访问其他的 + PermissionStatus status = PermissionStatus.granted; + status = await _checkSinglePermission(Permission.locationWhenInUse); + + // 获取第二个状态 + PermissionStatus status2 = PermissionStatus.denied; + + // 如果前置状态为成功才能执行获取第二个状态 + if (status.isGranted) { + status2 = await _checkSinglePermission(Permission.locationAlways); + } + + // 如果两个都成功那么就返回成功 + if (status.isGranted && status2.isGranted) { + return true; + + // 如果有一个拒绝那么就失败了 + } else if (status.isDenied || status2.isDenied) { + _showFailedDialog( + [Permission.locationWhenInUse, Permission.locationAlways]); + } else { + _showFailedDialog( + [Permission.locationWhenInUse, Permission.locationAlways], + isPermanentlyDenied: true, + ); + } + return false; + } + + static _checkSinglePermission(Permission permission) async { + // 获取当前状态 + PermissionStatus status = await permission.status; + PermissionStatus currentPermissionStatus = PermissionStatus.granted; + + // 如果它状态不是允许那么就去获取 + if (!status.isGranted) { + currentPermissionStatus = await _requestPermission([permission]); + } + + // 返回最终状态 + return currentPermissionStatus; + } + + /// 权限拒绝后弹窗 + static _showFailedDialog(List permissionList, {bool isPermanentlyDenied = false}) async { + Get.dialog( + barrierDismissible: false, + HintDialog( + content: await _getInstructions(permissionList), + confirmText: isPermanentlyDenied ? 'Go Settings' : 'Confirm', + confirmOnTap: () { + if (isPermanentlyDenied) { + openAppSettings(); + } else { + checkPermission(permissionList); + } + }, + ), + ); + } + + /// 获取权限使用说明 + static Future _getInstructions(List permissionList) async { + late Permission failedPermission; + + // 遍历当前权限申请列表 + for (Permission permission in permissionList) { + PermissionStatus status = await permission.status; + + // 如果不是允许状态就添加到新的申请列表中 + if (!status.isGranted || !status.isLimited) { + failedPermission = permission; + break; + } + } + String explain = ''; + if (failedPermission == Permission.storage) { + explain = 'Please allow the APP to save photos to the album'; + } + return explain; + } +} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart new file mode 100644 index 0000000..53133f6 --- /dev/null +++ b/lib/generated/assets.dart @@ -0,0 +1,9 @@ +///This file is automatically generated. DO NOT EDIT, all your changes would be lost. +class Assets { + Assets._(); + + static const String imagesError = 'assets/images/error.png'; + static const String imagesPlaceholder = 'assets/images/placeholder.png'; + static const String jsonWallpaper = 'assets/json/wallpaper.json'; + +} diff --git a/lib/generated/json/base/json_convert_content.dart b/lib/generated/json/base/json_convert_content.dart new file mode 100644 index 0000000..fa80107 --- /dev/null +++ b/lib/generated/json/base/json_convert_content.dart @@ -0,0 +1,186 @@ +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: camel_case_types +// ignore_for_file: prefer_single_quotes + +// This file is automatically generated. DO NOT EDIT, all your changes would be lost. +import 'package:flutter/material.dart' show debugPrint; +import 'package:wallpaper/common/models/base_resp_model.dart'; +import 'package:wallpaper/common/models/wallpaper_model.dart'; + +JsonConvert jsonConvert = JsonConvert(); + +typedef JsonConvertFunction = T Function(Map json); +typedef EnumConvertFunction = T Function(String value); +typedef ConvertExceptionHandler = void Function(Object error, StackTrace stackTrace); +extension MapSafeExt on Map { + T? getOrNull(K? key) { + if (!containsKey(key) || key == null) { + return null; + } else { + return this[key] as T?; + } + } +} + +class JsonConvert { + static ConvertExceptionHandler? onError; + JsonConvertClassCollection convertFuncMap = JsonConvertClassCollection(); + + /// When you are in the development, to generate a new model class, hot-reload doesn't find new generation model class, you can build on MaterialApp method called jsonConvert. ReassembleConvertFuncMap (); This method only works in a development environment + /// https://flutter.cn/docs/development/tools/hot-reload + /// class MyApp extends StatelessWidget { + /// const MyApp({Key? key}) + /// : super(key: key); + /// + /// @override + /// Widget build(BuildContext context) { + /// jsonConvert.reassembleConvertFuncMap(); + /// return MaterialApp(); + /// } + /// } + void reassembleConvertFuncMap() { + bool isReleaseMode = const bool.fromEnvironment('dart.vm.product'); + if (!isReleaseMode) { + convertFuncMap = JsonConvertClassCollection(); + } + } + + T? convert(dynamic value, {EnumConvertFunction? enumConvert}) { + if (value == null) { + return null; + } + if (value is T) { + return value; + } + try { + return _asT(value, enumConvert: enumConvert); + } catch (e, stackTrace) { + debugPrint('asT<$T> $e $stackTrace'); + if (onError != null) { + onError!(e, stackTrace); + } + return null; + } + } + + List? convertList(List? value, + {EnumConvertFunction? enumConvert}) { + if (value == null) { + return null; + } + try { + return value.map((dynamic e) => _asT(e, enumConvert: enumConvert)) + .toList(); + } catch (e, stackTrace) { + debugPrint('asT<$T> $e $stackTrace'); + if (onError != null) { + onError!(e, stackTrace); + } + return []; + } + } + + List? convertListNotNull(dynamic value, + {EnumConvertFunction? enumConvert}) { + if (value == null) { + return null; + } + try { + return (value as List).map((dynamic e) => + _asT(e, enumConvert: enumConvert)!).toList(); + } catch (e, stackTrace) { + debugPrint('asT<$T> $e $stackTrace'); + if (onError != null) { + onError!(e, stackTrace); + } + return []; + } + } + + T? _asT(dynamic value, + {EnumConvertFunction? enumConvert}) { + final String type = T.toString(); + final String valueS = value.toString(); + if (enumConvert != null) { + return enumConvert(valueS) as T; + } else if (type == "String") { + return valueS as T; + } else if (type == "int") { + final int? intValue = int.tryParse(valueS); + if (intValue == null) { + return double.tryParse(valueS)?.toInt() as T?; + } else { + return intValue as T; + } + } else if (type == "double") { + return double.parse(valueS) as T; + } else if (type == "DateTime") { + return DateTime.parse(valueS) as T; + } else if (type == "bool") { + if (valueS == '0' || valueS == '1') { + return (valueS == '1') as T; + } + return (valueS == 'true') as T; + } else if (type == "Map" || type.startsWith("Map<")) { + return value as T; + } else { + if (convertFuncMap.containsKey(type)) { + if (value == null) { + return null; + } + return convertFuncMap[type]!(value as Map) as T; + } else { + throw UnimplementedError( + '$type unimplemented,you can try running the app again'); + } + } + } + + //list is returned by type + static M? _getListChildType(List> data) { + if ([] is M) { + return data.map((Map e) => + BaseRespModel.fromJson(e)).toList() as M; + } + if ([] is M) { + return data.map((Map e) => + WallpaperModel.fromJson(e)).toList() as M; + } + if ([] is M) { + return data.map((Map e) => + WallpaperData.fromJson(e)).toList() as M; + } + + debugPrint("$M not found"); + + return null; + } + + static M? fromJsonAsT(dynamic json) { + if (json is M) { + return json; + } + if (json is List) { + return _getListChildType( + json.map((dynamic e) => e as Map).toList()); + } else { + return jsonConvert.convert(json); + } + } +} + +class JsonConvertClassCollection { + Map convertFuncMap = { + (BaseRespModel).toString(): BaseRespModel.fromJson, + (WallpaperModel).toString(): WallpaperModel.fromJson, + (WallpaperData).toString(): WallpaperData.fromJson, + }; + + bool containsKey(String type) { + return convertFuncMap.containsKey(type); + } + + JsonConvertFunction? operator [](String key) { + return convertFuncMap[key]; + } +} \ No newline at end of file diff --git a/lib/generated/json/base/json_field.dart b/lib/generated/json/base/json_field.dart new file mode 100644 index 0000000..8c967d4 --- /dev/null +++ b/lib/generated/json/base/json_field.dart @@ -0,0 +1,32 @@ +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: camel_case_types +// ignore_for_file: prefer_single_quotes + +// This file is automatically generated. DO NOT EDIT, all your changes would be lost. + +import 'package:meta/meta_meta.dart'; + +@Target({TargetKind.classType}) +class JsonSerializable { + const JsonSerializable(); +} + +@Target({TargetKind.field}) +class JSONField { + //Specify the parse field name + final String? name; + + //Whether to participate in toJson + final bool? serialize; + + //Whether to participate in fromMap + final bool? deserialize; + + //Whether to participate in copyWith + final bool? copyWith; + + //Enumeration or not + final bool? isEnum; + + const JSONField({this.name, this.serialize, this.deserialize, this.isEnum, this.copyWith}); +} diff --git a/lib/generated/json/base_resp_model.g.dart b/lib/generated/json/base_resp_model.g.dart new file mode 100644 index 0000000..c0252c1 --- /dev/null +++ b/lib/generated/json/base_resp_model.g.dart @@ -0,0 +1,40 @@ +import 'package:wallpaper/generated/json/base/json_convert_content.dart'; +import 'package:wallpaper/common/models/base_resp_model.dart'; + +BaseRespModel $BaseRespModelFromJson(Map json) { + final BaseRespModel baseRespModel = BaseRespModel(); + final int? code = jsonConvert.convert(json['code']); + if (code != null) { + baseRespModel.code = code; + } + final String? message = jsonConvert.convert(json['message']); + if (message != null) { + baseRespModel.message = message; + } + final dynamic data = json['data']; + if (data != null) { + baseRespModel.data = data; + } + return baseRespModel; +} + +Map $BaseRespModelToJson(BaseRespModel entity) { + final Map data = {}; + data['code'] = entity.code; + data['message'] = entity.message; + data['data'] = entity.data; + return data; +} + +extension BaseRespModelExtension on BaseRespModel { + BaseRespModel copyWith({ + int? code, + String? message, + dynamic data, + }) { + return BaseRespModel() + ..code = code ?? this.code + ..message = message ?? this.message + ..data = data ?? this.data; + } +} \ No newline at end of file diff --git a/lib/generated/json/wallpaper_model.g.dart b/lib/generated/json/wallpaper_model.g.dart new file mode 100644 index 0000000..fb6f72d --- /dev/null +++ b/lib/generated/json/wallpaper_model.g.dart @@ -0,0 +1,84 @@ +import 'package:wallpaper/generated/json/base/json_convert_content.dart'; +import 'package:wallpaper/common/models/wallpaper_model.dart'; +import 'package:hive/hive.dart'; + + +WallpaperModel $WallpaperModelFromJson(Map json) { + final WallpaperModel wallpaperModel = WallpaperModel(); + final List? data = (json['data'] as List?) + ?.map( + (e) => jsonConvert.convert(e) as WallpaperData) + .toList(); + if (data != null) { + wallpaperModel.data = data; + } + final String? name = jsonConvert.convert(json['name']); + if (name != null) { + wallpaperModel.name = name; + } + return wallpaperModel; +} + +Map $WallpaperModelToJson(WallpaperModel entity) { + final Map data = {}; + data['data'] = entity.data?.map((v) => v.toJson()).toList(); + data['name'] = entity.name; + return data; +} + +extension WallpaperModelExtension on WallpaperModel { + WallpaperModel copyWith({ + List? data, + String? name, + }) { + return WallpaperModel() + ..data = data ?? this.data + ..name = name ?? this.name; + } +} + +WallpaperData $WallpaperDataFromJson(Map json) { + final WallpaperData wallpaperData = WallpaperData(); + final String? banner = jsonConvert.convert(json['banner']); + if (banner != null) { + wallpaperData.banner = banner; + } + final String? original = jsonConvert.convert(json['original']); + if (original != null) { + wallpaperData.original = original; + } + final String? previewThumb = jsonConvert.convert( + json['previewThumb']); + if (previewThumb != null) { + wallpaperData.previewThumb = previewThumb; + } + final String? source = jsonConvert.convert(json['source']); + if (source != null) { + wallpaperData.source = source; + } + return wallpaperData; +} + +Map $WallpaperDataToJson(WallpaperData entity) { + final Map data = {}; + data['banner'] = entity.banner; + data['original'] = entity.original; + data['previewThumb'] = entity.previewThumb; + data['source'] = entity.source; + return data; +} + +extension WallpaperDataExtension on WallpaperData { + WallpaperData copyWith({ + String? banner, + String? original, + String? previewThumb, + String? source, + }) { + return WallpaperData() + ..banner = banner ?? this.banner + ..original = original ?? this.original + ..previewThumb = previewThumb ?? this.previewThumb + ..source = source ?? this.source; + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 944c779..4eac839 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,17 +1,28 @@ +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'common/components/easy_loading.dart'; -import 'common/config/strings.dart'; -import 'common/theme/themes.dart'; -import 'routes/app_pages.dart'; +import 'package:wallpaper/common/components/easy_loading.dart'; +import 'package:wallpaper/common/storage/hive_storage.dart'; +import 'package:wallpaper/res/themes/app_themes.dart'; +import 'package:wallpaper/res/values/strings.dart'; +import 'package:wallpaper/routes/app_pages.dart'; void main() async { + // 初始化Hive + await initHive(); + runApp(const MyApp()); // EasyLoading配置 configLoading(); + + // 沉浸式状态栏 + if(Platform.isAndroid) { + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(statusBarColor: Colors.transparent)); + } } class MyApp extends StatelessWidget { @@ -22,7 +33,7 @@ class MyApp extends StatelessWidget { final easyLoading = EasyLoading.init(); return ScreenUtilInit( // 设计稿中设备的尺寸(单位随意,建议dp,但在使用过程中必须保持一致) - designSize: const Size(360, 690), + designSize: const Size(375, 812), // 是否根据宽度/高度中的最小值适配文字 minTextAdapt: true, // 支持分屏尺寸 @@ -30,11 +41,11 @@ class MyApp extends StatelessWidget { builder: (context, child) { return GetMaterialApp( title: appName, - theme: lightTheme, + theme: darkTheme, darkTheme: darkTheme, - themeMode: ThemeMode.light, + themeMode: ThemeMode.dark, getPages: AppPages.routes, - initialRoute: AppPages.home, + initialRoute: AppPages.root, builder: (context, widget) { widget = easyLoading(context, widget); return MediaQuery( diff --git a/lib/modules/about/about_binding.dart b/lib/modules/about/about_binding.dart new file mode 100644 index 0000000..52fdf9c --- /dev/null +++ b/lib/modules/about/about_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'about_controller.dart'; + +class AboutBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => AboutController()); + } +} diff --git a/lib/modules/about/about_controller.dart b/lib/modules/about/about_controller.dart new file mode 100644 index 0000000..ed97a62 --- /dev/null +++ b/lib/modules/about/about_controller.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:wallpaper/res/values/strings.dart'; + +class AboutController extends GetxController { + var versionName = ''.obs; + + @override + void onReady() { + super.onReady(); + _getVersion(); + } + + /// 获取版本号 + void _getVersion() async { + final packageInfo = await PackageInfo.fromPlatform(); + versionName.value = '$appName\nversion:${packageInfo.version}'; + } +} diff --git a/lib/modules/about/about_view.dart b/lib/modules/about/about_view.dart new file mode 100644 index 0000000..e5183aa --- /dev/null +++ b/lib/modules/about/about_view.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/base_appbar.dart'; + +import 'about_controller.dart'; + +class AboutView extends GetView { + const AboutView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const BaseAppBar('About'), + body: Column( + children: [ + SizedBox(height: 100.h), + Obx(() { + return Align( + alignment: Alignment.center, + child: Text( + controller.versionName.value, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 14.sp, + ), + ), + ); + }), + ], + ), + ); + } +} diff --git a/lib/modules/favorite/favorite_controller.dart b/lib/modules/favorite/favorite_controller.dart new file mode 100644 index 0000000..e652640 --- /dev/null +++ b/lib/modules/favorite/favorite_controller.dart @@ -0,0 +1,74 @@ +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/dialog/hint_dialog.dart'; +import 'package:wallpaper/common/components/view_state_widget.dart'; +import 'package:wallpaper/common/models/wallpaper_model.dart'; +import 'package:wallpaper/common/storage/favorite_data.dart'; +import 'package:wallpaper/routes/app_pages.dart'; + +class FavoriteController extends GetxController { + static FavoriteController get to => Get.find(); + var viewState = ViewState.loading; + late List wallpaperList; + + @override + void onInit() { + super.onInit(); + _getData(); + } + + /// 获取数据 + void _getData() { + wallpaperList = FavoriteData().getWallpaperData().reversed.toList(); + _changeViewState(); + } + + /// 改变页面状态 + void _changeViewState() { + viewState = wallpaperList.isNotEmpty ? ViewState.normal : ViewState.empty; + } + + /// 点击壁纸 + void itemOnTap(int index) async { + Get.toNamed(AppPages.wallpaperDet, arguments: { + 'isFavorite': true, + 'position': index, + 'wallpaperList': wallpaperList + }); + } + + /// 长按壁纸 + void onLongPress(int index) { + Get.dialog( + barrierDismissible: false, + HintDialog( + content: 'Are you sure you want to delete the wallpaper?', + confirmOnTap: () { + FavoriteData().delete(index); + refreshList(); + }, + ), + ); + } + + /// 删除所有壁纸 + void deleteAll() { + Get.dialog( + barrierDismissible: false, + HintDialog( + content: 'Are you sure to delete all wallpapers?', + confirmOnTap: () { + FavoriteData().clear(); + wallpaperList.clear(); + _changeViewState(); + refresh(); + }, + ), + ); + } + + /// 刷新 + void refreshList() { + _getData(); + refresh(); + } +} diff --git a/lib/modules/favorite/favorite_view.dart b/lib/modules/favorite/favorite_view.dart new file mode 100644 index 0000000..0ca66b4 --- /dev/null +++ b/lib/modules/favorite/favorite_view.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/base_masonry_gridView.dart'; +import 'package:wallpaper/common/components/title_bar_widget.dart'; +import 'package:wallpaper/modules/favorite/favorite_controller.dart'; + +class FavoriteView extends GetView { + const FavoriteView({super.key}); + + @override + Widget build(BuildContext context) { + Get.lazyPut(() => FavoriteController()); + return SafeArea( + child: GetBuilder( + builder: (logic) { + return Column( + children: [ + TitleBarWidget( + title: 'Favorites', + showMenuBtn: controller.wallpaperList.isNotEmpty, + deleteOnTap: controller.deleteAll, + ), + Expanded( + child: BaseMasonryGridView( + viewState: controller.viewState, + wallpaperList: controller.wallpaperList, + itemOnTap: (index) => controller.itemOnTap(index), + onLongPress: (index) => controller.onLongPress(index), + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/modules/home/home_binding.dart b/lib/modules/home/home_binding.dart deleted file mode 100644 index 0905989..0000000 --- a/lib/modules/home/home_binding.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:get/get.dart'; - -import 'home_controller.dart'; - -class HomeBinding extends Bindings { - @override - void dependencies() { - Get.lazyPut(() => HomeController()); - } -} diff --git a/lib/modules/home/home_cls/home_cls_controller.dart b/lib/modules/home/home_cls/home_cls_controller.dart new file mode 100644 index 0000000..f260713 --- /dev/null +++ b/lib/modules/home/home_cls/home_cls_controller.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; +import 'package:wallpaper/common/models/wallpaper_model.dart'; +import 'package:wallpaper/modules/home/home_controller.dart'; +import 'package:wallpaper/routes/app_pages.dart'; + +class HomeClsController extends GetxController { + late List wallpaperList; + + /// 通过下标获取对应分类的壁纸 + void initData(int index) { + WallpaperModel wallpaperModel = HomeController.to.getWallpaperData(index); + wallpaperList = wallpaperModel.data ?? []; + } + + /// 点击壁纸 + void itemOnTap(int index) async { + Get.toNamed(AppPages.wallpaperDet, arguments: {'position': index, 'wallpaperList': wallpaperList}); + } +} diff --git a/lib/modules/home/home_cls/home_cls_view.dart b/lib/modules/home/home_cls/home_cls_view.dart new file mode 100644 index 0000000..df24593 --- /dev/null +++ b/lib/modules/home/home_cls/home_cls_view.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/base_masonry_gridView.dart'; +import 'package:wallpaper/modules/home/home_cls/home_cls_controller.dart'; + +class HomeClsView extends GetView { + const HomeClsView({super.key, required this.index}); + final int index; + + @override + String? get tag => index.toString(); + + @override + Widget build(BuildContext context) { + Get.lazyPut(() => HomeClsController(), tag: tag); + controller.initData(index); + // return GridView.builder( + // physics: const BouncingScrollPhysics(), + // padding: EdgeInsets.zero, + // itemCount: controller.wallpaperList.length, + // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + // crossAxisCount: 2, + // mainAxisSpacing: ScreenAdapter.w(8), + // crossAxisSpacing: ScreenAdapter.w(8), + // childAspectRatio: 0.6, + // ), + // itemBuilder: (context, index) { + // return _wallpaperItem(controller.wallpaperList[index]); + // }, + // ); + return BaseMasonryGridView( + wallpaperList: controller.wallpaperList, + itemOnTap: (index) => controller.itemOnTap(index), + ); + } +} diff --git a/lib/modules/home/home_controller.dart b/lib/modules/home/home_controller.dart index 9f5a6fc..e667073 100644 --- a/lib/modules/home/home_controller.dart +++ b/lib/modules/home/home_controller.dart @@ -1,5 +1,40 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:wallpaper/common/models/wallpaper_model.dart'; +import 'package:wallpaper/generated/assets.dart'; +import 'package:wallpaper/generated/json/base/json_convert_content.dart'; +import 'package:wallpaper/modules/home/home_cls/home_cls_view.dart'; -class HomeController extends GetxController { +class HomeController extends GetxController with GetTickerProviderStateMixin { + static HomeController get to => Get.find(); + late TabController tabController; + var wallpaperModelList = []; + var clsPages = []; + @override + void onReady() async { + super.onReady(); + // 读取json文件,获取数据 + var data = jsonDecode(await rootBundle.loadString(Assets.jsonWallpaper)); + if (data != null && data is List) { + wallpaperModelList = data.map((e) => WallpaperModel.fromJson(e)).toList(); + } + wallpaperModelList = JsonConvert.fromJsonAsT>(data) ?? []; + tabController = TabController(length: wallpaperModelList.length, vsync: this); + clsPages = wallpaperModelList.asMap().entries.map((e) => HomeClsView(index: e.key)).toList(); + refresh(); + } + + @override + void onClose() { + tabController.dispose(); + super.onClose(); + } + + /// 获取分类下的壁纸 + WallpaperModel getWallpaperData(int index) { + return wallpaperModelList[index]; + } } diff --git a/lib/modules/home/home_view.dart b/lib/modules/home/home_view.dart index 56ed3cb..ef94f3b 100644 --- a/lib/modules/home/home_view.dart +++ b/lib/modules/home/home_view.dart @@ -1,19 +1,47 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; - -import 'home_controller.dart'; +import 'package:wallpaper/common/components/keep_alive_wrapper.dart'; +import 'package:wallpaper/modules/home/home_controller.dart'; class HomeView extends GetView { const HomeView({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Wallpaper'), + Get.lazyPut(() => HomeController()); + return SafeArea( + child: GetBuilder( + builder: (controller) { + return Column( + children: [ + if (controller.wallpaperModelList.isNotEmpty) ...[ + _tabBar(), + _tabBarView(), + ], + ], + ); + }, ), - body: const Center( - child: Text('Wallpaper'), + ); + } + + Widget _tabBar() { + return TabBar( + isScrollable: true, + labelPadding: const EdgeInsets.symmetric(horizontal: 8.0).w, + indicatorPadding: const EdgeInsets.only(bottom: 6).h, + controller: controller.tabController, + tabs: controller.wallpaperModelList.map((e) => Tab(text: '${e.name}')).toList(), + ); + } + + Widget _tabBarView() { + return Expanded( + child: TabBarView( + physics: const BouncingScrollPhysics(), + controller: controller.tabController, + children: controller.clsPages.map((e) => KeepAliveWrapper(child: e)).toList(), ), ); } diff --git a/lib/modules/me/me_controller.dart b/lib/modules/me/me_controller.dart new file mode 100644 index 0000000..7eadcf8 --- /dev/null +++ b/lib/modules/me/me_controller.dart @@ -0,0 +1,16 @@ +import 'package:get/get.dart'; +import 'package:wallpaper/routes/app_pages.dart'; + +class MeController extends GetxController { + // final options = ['About', 'Feedback', 'Share', 'Privacy Policy', 'Terms of Service']; + final options = ['About', 'Privacy Policy', 'Terms of Service']; + + void itemOnTap(int index) { + if (index == 0) { + Get.toNamed(AppPages.about); + } + if (index == 1 || index == 2) { + Get.toNamed(AppPages.webPage, arguments: {'title': options[index], 'url': null}); + } + } +} diff --git a/lib/modules/me/me_view.dart b/lib/modules/me/me_view.dart new file mode 100644 index 0000000..e6734ba --- /dev/null +++ b/lib/modules/me/me_view.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/title_bar_widget.dart'; +import 'package:wallpaper/modules/me/me_controller.dart'; + +class MeView extends GetView { + const MeView({super.key}); + + @override + Widget build(BuildContext context) { + Get.lazyPut(() => MeController()); + return SafeArea( + child: Column( + children: [ + const TitleBarWidget(title: 'Me'), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.symmetric(horizontal: 16).w, + itemCount: controller.options.length, + itemBuilder: (context, index) { + return _optionItem(index); + }, + separatorBuilder: (context, index) { + return SizedBox(height: 10.h); + }, + ), + ), + ], + ), + ); + } + + Widget _optionItem(index) { + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(8).r, + onTap: () => controller.itemOnTap(index), + child: Container( + height: 50.h, + padding: const EdgeInsets.symmetric(horizontal: 16).w, + decoration: BoxDecoration( + color: Colors.white10, + borderRadius: BorderRadius.circular(8).r, + ), + child: Row( + children: [ + Expanded( + child: Text( + controller.options[index], + style: TextStyle( + color: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + const Icon(Icons.keyboard_arrow_right, color: Colors.white), + ], + ), + ), + ), + ); + } +} diff --git a/lib/modules/root/root_binding.dart b/lib/modules/root/root_binding.dart new file mode 100644 index 0000000..86d5751 --- /dev/null +++ b/lib/modules/root/root_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:wallpaper/modules/root/root_controller.dart'; + +class RootBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => RootController()); + } +} diff --git a/lib/modules/root/root_controller.dart b/lib/modules/root/root_controller.dart new file mode 100644 index 0000000..7217513 --- /dev/null +++ b/lib/modules/root/root_controller.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/modules/favorite/favorite_view.dart'; +import 'package:wallpaper/modules/home/home_view.dart'; +import 'package:wallpaper/modules/me/me_view.dart'; + +class RootController extends GetxController { + static RootController get to => Get.find(); + final pages = [ + PageItem('Home', Icons.home, const HomeView()), + PageItem('Favorite', Icons.favorite, const FavoriteView()), + PageItem('Me', Icons.person, const MeView()), + ]; + late PageController pageController; + var currentIndex = 0.obs; + + @override + void onInit() { + super.onInit(); + pageController = PageController(initialPage: currentIndex.value); + } + + @override + void onClose() { + pageController.dispose(); + super.onClose(); + } + + /// PageView页面变化 + void onPageChanged(int index) { + currentIndex.value = index; + } + + /// BottomNavigationBar切换 + void onTabTapped(int index) { + pageController.jumpToPage(index); + } +} + +class PageItem { + late final String label; + late final IconData icons; + late final StatelessWidget widget; + + PageItem(this.label, this.icons, this.widget); +} diff --git a/lib/modules/root/root_view.dart b/lib/modules/root/root_view.dart new file mode 100644 index 0000000..a4eed8d --- /dev/null +++ b/lib/modules/root/root_view.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/keep_alive_wrapper.dart'; +import 'package:wallpaper/modules/root/root_controller.dart'; + +class RootView extends GetView { + const RootView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: PageView( + physics: const NeverScrollableScrollPhysics(), + controller: controller.pageController, + onPageChanged: (index) => controller.onPageChanged(index), + children: controller.pages.map((e) => KeepAliveWrapper(child: e.widget)).toList(), + ), + bottomNavigationBar: Obx(() { + return BottomNavigationBar( + currentIndex: controller.currentIndex.value, + onTap: (index) => controller.onTabTapped(index), + items: _bottomNavigationBarItems(), + ); + }), + ); + } + + List _bottomNavigationBarItems() { + return controller.pages.map((e) { + return BottomNavigationBarItem( + icon: Icon(e.icons), + label: e.label, + ); + }).toList(); + } +} diff --git a/lib/modules/wallpaper_det/wallpaper_det_binding.dart b/lib/modules/wallpaper_det/wallpaper_det_binding.dart new file mode 100644 index 0000000..ba240ec --- /dev/null +++ b/lib/modules/wallpaper_det/wallpaper_det_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'wallpaper_det_controller.dart'; + +class WallpaperDetBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => WallpaperDetController()); + } +} diff --git a/lib/modules/wallpaper_det/wallpaper_det_controller.dart b/lib/modules/wallpaper_det/wallpaper_det_controller.dart new file mode 100644 index 0000000..1698549 --- /dev/null +++ b/lib/modules/wallpaper_det/wallpaper_det_controller.dart @@ -0,0 +1,105 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:wallpaper/common/components/easy_loading.dart'; +import 'package:wallpaper/common/components/wallpaper_picker.dart'; +import 'package:wallpaper/common/models/wallpaper_model.dart'; +import 'package:wallpaper/common/storage/favorite_data.dart'; +import 'package:wallpaper/common/utils/device_info_util.dart'; +import 'package:wallpaper/common/utils/download_util.dart'; +import 'package:wallpaper/common/utils/obj_util.dart'; +import 'package:wallpaper/common/utils/permission_util.dart'; +import 'package:wallpaper/modules/favorite/favorite_controller.dart'; + +class WallpaperDetController extends GetxController { + late int position; + late final List wallpaperList; + late final PageController pageController; + final options = [OptionItem('Download', Icons.download)]; + var filePath = ''; + + @override + void onInit() { + super.onInit(); + bool isFavorite = Get.arguments['isFavorite'] ?? false; + position = Get.arguments['position'] ?? 0; + wallpaperList = Get.arguments['wallpaperList'] ?? []; + pageController = PageController(initialPage: position); + if (!isFavorite) { + options.add(OptionItem('Favorite', Icons.favorite)); + } + if (Platform.isAndroid) { + options.add(OptionItem('Set Wallpaper', Icons.wallpaper)); + } + } + + /// 图片切换 + void onPageChanged(int index) { + position = index; + filePath = ''; + } + + /// 选项事件 + void optionOnTap(int index) { + if (index == 0) { + // 下载 + if (Get.isSnackbarOpen) { + return; + } + DownloadUtil.downloadWallpaper('${wallpaperList[position].original}', (savePath) async { + filePath = savePath; + bool canSave = true; + if (Platform.isAndroid) { + // 在Android 10及以上版本,由于引入了分区存储(Scoped Storage)机制,如果应用只是需要访问媒体文件,而不是整个外部存储 + // ImageGallerySaver使用MediaStore API来添加图片到相册,无需请求存储权限 + // Android 10以下版本,需要申请存储权限 + int sdkVersion = await DeviceInfoUtil.getSDKVersion(); + if (sdkVersion < 29) { + bool status = await PermissionUtil.checkPermission([Permission.storage]); + canSave = status; + } + } + if (canSave) { + final result = await ImageGallerySaver.saveFile(savePath); + if (result['isSuccess']) { + toast('Saved to album'); + } else { + toast('Unable to save to album'); + } + } + }); + } else if (index == 1) { + // 收藏 + WallpaperData wallpaperData = wallpaperList[position]; + if (FavoriteData().getWallpaperData().firstWhereOrNull((element) => element.original == wallpaperData.original) != null) { + toast('Wallpaper has been collected'); + return; + } + FavoriteData().setWallpaperData(wallpaperData); + toast('Wallpaper has been collected'); + // 刷新收藏列表 + if (Get.isRegistered()) { + FavoriteController.to.refreshList(); + } + } else { + // 设置壁纸 + if (ObjUtil.isNotEmptyStr(filePath)) { + WallpaperPicker.picker(filePath); + } else { + DownloadUtil.downloadWallpaper('${wallpaperList[position].original}', (savePath) async { + filePath = savePath; + WallpaperPicker.picker(savePath); + }); + } + } + } +} + +class OptionItem { + late final String option; + late final IconData icons; + + OptionItem(this.option, this.icons); +} diff --git a/lib/modules/wallpaper_det/wallpaper_det_view.dart b/lib/modules/wallpaper_det/wallpaper_det_view.dart new file mode 100644 index 0000000..075aac4 --- /dev/null +++ b/lib/modules/wallpaper_det/wallpaper_det_view.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; +import 'package:wallpaper/common/components/base_appbar.dart'; +import 'package:wallpaper/common/components/view_state_widget.dart'; +import 'package:wallpaper/modules/wallpaper_det/wallpaper_det_controller.dart'; + +class WallpaperDetView extends GetView { + const WallpaperDetView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBodyBehindAppBar: true, + appBar: const BaseAppBar('', backgroundColor: Colors.transparent, iconColor: Colors.white), + body: Stack( + alignment: Alignment.center, + children: [ + _photoView(), + _bottomOption(), + ], + ), + ); + } + + /// 图片查看器 + Widget _photoView() { + return PhotoViewGallery.builder( + itemCount: controller.wallpaperList.length, + builder: (BuildContext context, int index) { + return PhotoViewGalleryPageOptions( + imageProvider: NetworkImage('${controller.wallpaperList[index].original}'), + initialScale: PhotoViewComputedScale.contained, + minScale: PhotoViewComputedScale.contained * 0.5, + maxScale: PhotoViewComputedScale.covered * 3, + heroAttributes: PhotoViewHeroAttributes(tag: controller.wallpaperList[index]), + // onTapUp: (context, details, controllerValue) => Get.back(), + ); + }, + loadingBuilder: (context, event) => loadingView( + // value: event == null ? 0 : event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 1), + ), + scrollPhysics: const BouncingScrollPhysics(), + backgroundDecoration: const BoxDecoration(color: Colors.black), + pageController: controller.pageController, + onPageChanged: (i) => controller.onPageChanged(i), + ); + } + + /// 底部选项 + Widget _bottomOption() { + return Positioned( + bottom: ScreenUtil().bottomBarHeight + 20.h, + child: Container( + width: 0.94.sw, + height: kBottomNavigationBarHeight, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(8).r, + ), + child: Row( + children: controller.options.asMap().entries.map((e) { + return Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(8).r, + onTap: () => controller.optionOnTap(e.key), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(e.value.icons, color: Colors.black), + Text( + e.value.option, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Colors.black, + fontSize: 14.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ); + }).toList(), + ), + ), + ); + } +} diff --git a/lib/modules/web_page/web_page_binding.dart b/lib/modules/web_page/web_page_binding.dart new file mode 100644 index 0000000..b6fd67e --- /dev/null +++ b/lib/modules/web_page/web_page_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'web_page_controller.dart'; + +class WebPageBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => WebPageController()); + } +} diff --git a/lib/modules/web_page/web_page_controller.dart b/lib/modules/web_page/web_page_controller.dart new file mode 100644 index 0000000..18170be --- /dev/null +++ b/lib/modules/web_page/web_page_controller.dart @@ -0,0 +1,38 @@ +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/view_state_widget.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class WebPageController extends GetxController { + var title = ''; + var url = ''; + late final WebViewController webViewController; + var viewState = ViewState.loading.obs; + + @override + void onInit() { + super.onInit(); + title = Get.arguments['title']; + url = Get.arguments['url'] ?? 'https://flutter.cn'; + webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(Get.theme.scaffoldBackgroundColor) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) { + viewState.value = ViewState.normal; + }, + onWebResourceError: (WebResourceError error) { + viewState.value = ViewState.error; + }, + onNavigationRequest: (NavigationRequest request) { + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse(url)); + } +} diff --git a/lib/modules/web_page/web_page_view.dart b/lib/modules/web_page/web_page_view.dart new file mode 100644 index 0000000..0f68a20 --- /dev/null +++ b/lib/modules/web_page/web_page_view.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:wallpaper/common/components/base_appbar.dart'; +import 'package:wallpaper/common/components/view_state_widget.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'web_page_controller.dart'; + +class WebPageView extends GetView { + const WebPageView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: BaseAppBar(controller.title), + body: Obx(() { + return ViewStateWidget( + state: controller.viewState.value, + child: WebViewWidget(controller: controller.webViewController), + ); + }), + ); + } +} diff --git a/lib/res/themes/app_colors.dart b/lib/res/themes/app_colors.dart new file mode 100644 index 0000000..0a48ba7 --- /dev/null +++ b/lib/res/themes/app_colors.dart @@ -0,0 +1,8 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 颜色 + +import 'package:flutter/material.dart'; + +/// 主要颜色 +const seedColor = Colors.black; \ No newline at end of file diff --git a/lib/res/themes/app_sizes.dart b/lib/res/themes/app_sizes.dart new file mode 100644 index 0000000..60dd932 --- /dev/null +++ b/lib/res/themes/app_sizes.dart @@ -0,0 +1,9 @@ +// Author: fengshengxiong +// Date: 2024/5/8 +// Description: 大小 + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +/// 状态栏+导航栏 +double statusToolBarHeight = kToolbarHeight + ScreenUtil().statusBarHeight; \ No newline at end of file diff --git a/lib/common/theme/styles.dart b/lib/res/themes/app_styles.dart similarity index 100% rename from lib/common/theme/styles.dart rename to lib/res/themes/app_styles.dart diff --git a/lib/res/themes/app_themes.dart b/lib/res/themes/app_themes.dart new file mode 100644 index 0000000..c08e626 --- /dev/null +++ b/lib/res/themes/app_themes.dart @@ -0,0 +1,48 @@ +// Author: fengshengxiong +// Date: 2024/5/7 +// Description: 主题 + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:wallpaper/res/themes/app_colors.dart'; + +/// 深色主题 +ThemeData darkTheme = ThemeData.dark(useMaterial3: true).copyWith( + colorScheme: ColorScheme.fromSeed(seedColor: seedColor, primary: Colors.white), + scaffoldBackgroundColor: Colors.black54, + appBarTheme: AppBarTheme( + elevation: 0.0, + centerTitle: true, + backgroundColor: Colors.black, + iconTheme: const IconThemeData( + color: Colors.white, + ), + titleTextStyle: TextStyle( + color: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + tabBarTheme: TabBarTheme( + dividerHeight: 0, + tabAlignment: TabAlignment.center, + indicatorColor: Colors.white, + labelStyle: TextStyle( + color: Colors.white, + fontSize: 15.sp, + fontWeight: FontWeight.w500, + ), + unselectedLabelStyle: TextStyle( + color: const Color(0xFF757575), + fontSize: 15.sp, + ), + ), + bottomNavigationBarTheme: BottomNavigationBarThemeData( + elevation: 0.0, + backgroundColor: Colors.black, + // selectedFontSize字体默认14会让BottomNavigationBar高度增加,56->58,因此将大小改为12 + selectedLabelStyle: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w500), + selectedItemColor: Colors.white, + unselectedItemColor: const Color(0xFF757575), + ), +); \ No newline at end of file diff --git a/lib/common/config/strings.dart b/lib/res/values/strings.dart similarity index 100% rename from lib/common/config/strings.dart rename to lib/res/values/strings.dart diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index bb76b57..b6ae5b0 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -3,22 +3,47 @@ // Description: 路由配置 import 'package:get/get.dart'; -import '../modules/home/home_binding.dart'; -import '../modules/home/home_view.dart'; +import 'package:wallpaper/modules/about/about_binding.dart'; +import 'package:wallpaper/modules/about/about_view.dart'; +import 'package:wallpaper/modules/root/root_binding.dart'; +import 'package:wallpaper/modules/root/root_view.dart'; +import 'package:wallpaper/modules/wallpaper_det/wallpaper_det_binding.dart'; +import 'package:wallpaper/modules/wallpaper_det/wallpaper_det_view.dart'; +import 'package:wallpaper/modules/web_page/web_page_binding.dart'; +import 'package:wallpaper/modules/web_page/web_page_view.dart'; class AppPages { AppPages._(); - /// 首页 - static const home = '/home'; + /// 根路由 + static const root = '/'; + /// 壁纸详情 + static const wallpaperDet = '/wallpaper_det'; + /// 关于 + static const about = '/about'; + /// WebView页面 + static const webPage = '/web_page'; static final routes = [ GetPage( - name: home, - page: () => const HomeView(), - bindings: [HomeBinding()], - participatesInRootNavigator: true, - preventDuplicates: true, + name: root, + page: () => const RootView(), + binding: RootBinding(), + ), + GetPage( + name: wallpaperDet, + page: () => const WallpaperDetView(), + binding: WallpaperDetBinding(), + ), + GetPage( + name: about, + page: () => const AboutView(), + binding: AboutBinding(), + ), + GetPage( + name: webPage, + page: () => const WebPageView(), + binding: WebPageBinding(), ), ]; } diff --git a/lib/utils/screen_adapter.dart b/lib/utils/screen_adapter.dart deleted file mode 100644 index 5d04152..0000000 --- a/lib/utils/screen_adapter.dart +++ /dev/null @@ -1,57 +0,0 @@ -// Author: fengshengxiong -// Date: 2024/5/7 -// Description: 屏幕适配器 - -import 'package:flutter_screenutil/flutter_screenutil.dart'; - -class ScreenAdapter { - /// 当前设备宽度 dp - static double getScreenWidth() { - return ScreenUtil().screenWidth; - } - - /// 当前设备高度 dp - static double getScreenHeight() { - return ScreenUtil().screenHeight; - } - - /// 状态栏高度 dp 刘海屏会更高 - static double getStatusBarHeight() { - return ScreenUtil().statusBarHeight; - } - - /// 底部安全区距离 dp - static double getBottomBarHeight() { - return ScreenUtil().bottomBarHeight; - } - - /// 屏幕宽度的倍数 - static double sw(num value) { - return value.sw; - } - - /// 屏幕高度的倍数 - static double sh(num value) { - return value.sh; - } - - /// [ScreenUtil.setWidth] - static double w(num value) { - return value.w; - } - - /// [ScreenUtil.setHeight] - static double h(num value) { - return value.h; - } - - /// [ScreenUtil.radius] - static double r(num value) { - return value.r; - } - - /// [ScreenUtil.setSp] - static double sp(num value) { - return value.sp; - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 677ea71..a4d7b36 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: # 网络库 dio: ^5.4.3+1 + pretty_dio_logger: ^1.3.1 # 屏幕和字体大小适配 flutter_screenutil: ^5.9.0 @@ -54,6 +55,37 @@ dependencies: # 日志打印 logger: ^2.2.0 + # 网格布局的集合(瀑布流) + flutter_staggered_grid_view: ^0.7.0 + + # 查找文件系统上的常用位置 + path_provider: ^2.1.3 + + # 将图像保存到图库中 + image_gallery_saver: ^2.0.3 + + # 缩放图像/内容小部件 + photo_view: ^0.15.0 + + # 权限处理 + permission_handler: ^11.3.1 + + # 获取当前设备信息 + device_info_plus: ^10.1.0 + + # 查询应用程序包信息 + package_info_plus: ^8.0.0 + + # Android 设备上设置壁纸。支持主屏幕、锁屏和双屏模式 + async_wallpaper: ^2.0.3 + + # 纯 Dart 编写的轻量级且速度极快的键值数据库 + hive: ^2.2.3 + hive_flutter: ^1.1.0 + + # WebView + webview_flutter: ^4.7.0 + dev_dependencies: flutter_test: sdk: flutter @@ -63,7 +95,9 @@ dev_dependencies: # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. - flutter_lints: ^3.0.2 + flutter_lints: ^4.0.0 + hive_generator: ^2.0.1 + build_runner: ^2.4.9 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -77,9 +111,13 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: + assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg + - assets/images/ + - assets/images/2.0x/ + - assets/images/3.0x/ + - assets/json/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware