第一版完成

This commit is contained in:
fengshengxiong 2024-05-13 13:44:27 +08:00
parent 73bb10f7f3
commit 1a271ba9b0
101 changed files with 15268 additions and 329 deletions

View File

@ -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

View File

@ -1,27 +1,38 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<application
android:label="@string/app_name"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
@ -37,8 +48,8 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<?xml version="1.0" encoding="utf-8"?><!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!--<item android:drawable="?android:colorBackground" />-->
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
android:src="@mipmap/ic_launcher" />
</item>
</layer-list>

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<?xml version="1.0" encoding="utf-8"?><!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<item>¬
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
android:src="@mipmap/ic_launcher" />
</item>
</layer-list>

BIN
assets/images/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

12208
assets/json/wallpaper.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,14 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
2DCFEA80A305BFF9A959A7DC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFDFEB7EDEB2089913ADD3B0 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
D52A56DB3AAE02F2724B4CBE /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F7EC70670285B234B265439 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -40,14 +42,19 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0F7EC70670285B234B265439 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
1561731E247DBD5F8A0E0B0F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
418D1D078E33D929E8A6B10D /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
47B01B907AB8EEA90704F2FE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7B71BC8DD4AFE8E93734BDDC /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -55,19 +62,44 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A037E86DEDE09232C797AA86 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
CFDFEB7EDEB2089913ADD3B0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D1C685EC8726F4DCDC6C4284 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
5AA6084520452439D2649B6D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D52A56DB3AAE02F2724B4CBE /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2DCFEA80A305BFF9A959A7DC /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0797B7C7F9044CE8DB306198 /* Pods */ = {
isa = PBXGroup;
children = (
D1C685EC8726F4DCDC6C4284 /* Pods-Runner.debug.xcconfig */,
47B01B907AB8EEA90704F2FE /* Pods-Runner.release.xcconfig */,
A037E86DEDE09232C797AA86 /* Pods-Runner.profile.xcconfig */,
418D1D078E33D929E8A6B10D /* Pods-RunnerTests.debug.xcconfig */,
1561731E247DBD5F8A0E0B0F /* Pods-RunnerTests.release.xcconfig */,
7B71BC8DD4AFE8E93734BDDC /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
@ -76,6 +108,15 @@
path = RunnerTests;
sourceTree = "<group>";
};
58D07E0F3F53151088D726CF /* Frameworks */ = {
isa = PBXGroup;
children = (
CFDFEB7EDEB2089913ADD3B0 /* Pods_Runner.framework */,
0F7EC70670285B234B265439 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -94,6 +135,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
0797B7C7F9044CE8DB306198 /* Pods */,
58D07E0F3F53151088D726CF /* Frameworks */,
);
sourceTree = "<group>";
};
@ -128,8 +171,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
9F3C7A1490C44408DFBA2D44 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
5AA6084520452439D2649B6D /* Frameworks */,
);
buildRules = (
);
@ -145,12 +190,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
34C811B39ECD78862B33AD2D /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
39CCEDF39CF579238AE308F0 /* [CP] Embed Pods Frameworks */,
405E88F0300BCE9602B4DA07 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -222,6 +270,45 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
34C811B39ECD78862B33AD2D /* [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-Runner-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;
};
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;

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -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
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

View File

@ -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.

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
@ -14,9 +16,15 @@
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFit" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
<rect key="frame" x="156.66666666666666" y="386" width="80" height="80"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="jA8-Ug-YSl"/>
<constraint firstAttribute="height" constant="80" id="uJb-kM-IXm"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -28,10 +36,10 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
<point key="canvasLocation" x="80.916030534351137" y="264.08450704225356"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchImage" width="1024" height="1024"/>
</resources>
</document>

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-16" y="-41"/>
</scene>
</scenes>
</document>

View File

@ -45,5 +45,7 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Please allow the APP to save photos to the album</string>
</dict>
</plist>

View File

@ -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<Widget>? 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);
}

View File

@ -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<WallpaperData> 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,
),
);
}
}

View File

@ -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,
),
),
),
),
),
),
);
}
}

View File

@ -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: <Widget>[
SizedBox(height: 10.h),
loadingView(
valueColor: const AlwaysStoppedAnimation<Color>(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,
),
),
),
),
],
],
),
),
),
),
),
),
);
}
}

View File

@ -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),
);
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,95 @@
// Author: fengshengxiong
// Date: 2024/5/7
// Description: GetXControllerGetXController无法回收的情况
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<GetxController>? binds;
final List<String>? tags;
final Widget child;
@override
GetBindWidgetState createState() => GetBindWidgetState();
}
class GetBindWidgetState extends State<GetBindWidget> {
@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);
}
}
}
}

View File

@ -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,
),
);
}
}

View File

@ -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<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}
class _KeepAliveWrapperState extends State<KeepAliveWrapper> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}
@override
bool get wantKeepAlive => widget.keepAlive;
}

View File

@ -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<String>(
value: 'delete',
onTap: deleteOnTap,
child: const ListTile(
leading: Icon(Icons.delete),
title: Text('Delete All'),
),
),
],
);
}
}

View File

@ -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<Color?>? 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,
),
),
);
}

View File

@ -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);
}
}

View File

@ -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/';
}

View File

@ -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<String, dynamic> json) => $BaseRespModelFromJson(json);
Map<String, dynamic> toJson() => $BaseRespModelToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}

View File

@ -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<WallpaperData>? data;
String? name;
WallpaperModel();
factory WallpaperModel.fromJson(Map<String, dynamic> json) => $WallpaperModelFromJson(json);
Map<String, dynamic> 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<String, dynamic> json) => $WallpaperDataFromJson(json);
Map<String, dynamic> toJson() => $WallpaperDataToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}

View File

@ -0,0 +1,49 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'wallpaper_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class WallpaperDataAdapter extends TypeAdapter<WallpaperData> {
@override
final int typeId = 1;
@override
WallpaperData read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
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;
}

View File

@ -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;
}

View File

@ -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<T>(
String path, {
required RequestMethod requestMethod,
dynamic data,
Map<String, dynamic>? 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<T>(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<void> download(
String urlPath,
dynamic savePath, {
ProgressCallback? onReceiveProgress,
Map<String, dynamic>? 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;
}

View File

@ -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);
}
}

View File

@ -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<WallpaperData> getWallpaperData() {
return _box.values.toList();
}
///
void setWallpaperData(WallpaperData wallpaperData) {
_box.add(wallpaperData);
}
///
void delete(index) {
_box.deleteAt(index);
}
///
void clear() {
_box.clear();
}
}

View File

@ -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<WallpaperData>(favoriteBox);
}
///
Box<WallpaperData> getFavoriteBox() {
return Hive.box<WallpaperData>(favoriteBox);
}

View File

@ -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);

View File

@ -1,3 +0,0 @@
// Author: fengshengxiong
// Date: 2024/5/8
// Description:

View File

@ -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,
);

View File

@ -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<int> getSDKVersion() async {
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
int sdkVersion = androidInfo.version.sdkInt;
return sdkVersion;
}
}

View File

@ -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<Color>(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();
});
}
}

View File

@ -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';
}
}
}

View File

@ -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<String> getTemporaryPath() async {
final dir = await getTemporaryDirectory();
return dir.path;
}
/// (IOS和Android通用)
/// Android: /data/user/0//files
static Future<String> getSupportPath() async {
final dir = await getApplicationSupportDirectory();
return dir.path;
}
/// (IOS和Android通用)
/// Android: /data/user/0//app_flutter
static Future<String> getDocumentsPath() async {
final dir = await getApplicationDocumentsDirectory();
return dir.path;
}
}

View File

@ -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(

View File

@ -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;
}
}

View File

@ -3,7 +3,7 @@
// Description:
class ObjUtil {
static bool isNotEmptyString(String? str) {
static bool isNotEmptyStr(String? str) {
return str == null || str.trim().isNotEmpty;
}

View File

@ -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<bool> checkPermission(List<Permission> permissionList) async {
//
List<Permission> 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<PermissionStatus> _requestPermission(List<Permission> permissionList) async {
Map<Permission, PermissionStatus> statuses = await permissionList.request();
PermissionStatus currentPermissionStatus = PermissionStatus.granted;
statuses.forEach((key, value) {
if (!value.isGranted || !value.isLimited) {
currentPermissionStatus = value;
return;
}
});
return currentPermissionStatus;
}
static Future<bool> 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<Permission> 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<String> _getInstructions(List<Permission> 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;
}
}

View File

@ -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';
}

View File

@ -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> = T Function(Map<String, dynamic> json);
typedef EnumConvertFunction<T> = T Function(String value);
typedef ConvertExceptionHandler = void Function(Object error, StackTrace stackTrace);
extension MapSafeExt<K, V> on Map<K, V> {
T? getOrNull<T>(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<T>(dynamic value, {EnumConvertFunction? enumConvert}) {
if (value == null) {
return null;
}
if (value is T) {
return value;
}
try {
return _asT<T>(value, enumConvert: enumConvert);
} catch (e, stackTrace) {
debugPrint('asT<$T> $e $stackTrace');
if (onError != null) {
onError!(e, stackTrace);
}
return null;
}
}
List<T?>? convertList<T>(List<dynamic>? value,
{EnumConvertFunction? enumConvert}) {
if (value == null) {
return null;
}
try {
return value.map((dynamic e) => _asT<T>(e, enumConvert: enumConvert))
.toList();
} catch (e, stackTrace) {
debugPrint('asT<$T> $e $stackTrace');
if (onError != null) {
onError!(e, stackTrace);
}
return <T>[];
}
}
List<T>? convertListNotNull<T>(dynamic value,
{EnumConvertFunction? enumConvert}) {
if (value == null) {
return null;
}
try {
return (value as List<dynamic>).map((dynamic e) =>
_asT<T>(e, enumConvert: enumConvert)!).toList();
} catch (e, stackTrace) {
debugPrint('asT<$T> $e $stackTrace');
if (onError != null) {
onError!(e, stackTrace);
}
return <T>[];
}
}
T? _asT<T extends Object?>(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<String, dynamic>) as T;
} else {
throw UnimplementedError(
'$type unimplemented,you can try running the app again');
}
}
}
//list is returned by type
static M? _getListChildType<M>(List<Map<String, dynamic>> data) {
if (<BaseRespModel>[] is M) {
return data.map<BaseRespModel>((Map<String, dynamic> e) =>
BaseRespModel.fromJson(e)).toList() as M;
}
if (<WallpaperModel>[] is M) {
return data.map<WallpaperModel>((Map<String, dynamic> e) =>
WallpaperModel.fromJson(e)).toList() as M;
}
if (<WallpaperData>[] is M) {
return data.map<WallpaperData>((Map<String, dynamic> e) =>
WallpaperData.fromJson(e)).toList() as M;
}
debugPrint("$M not found");
return null;
}
static M? fromJsonAsT<M>(dynamic json) {
if (json is M) {
return json;
}
if (json is List) {
return _getListChildType<M>(
json.map((dynamic e) => e as Map<String, dynamic>).toList());
} else {
return jsonConvert.convert<M>(json);
}
}
}
class JsonConvertClassCollection {
Map<String, JsonConvertFunction> 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];
}
}

View File

@ -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});
}

View File

@ -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<String, dynamic> json) {
final BaseRespModel baseRespModel = BaseRespModel();
final int? code = jsonConvert.convert<int>(json['code']);
if (code != null) {
baseRespModel.code = code;
}
final String? message = jsonConvert.convert<String>(json['message']);
if (message != null) {
baseRespModel.message = message;
}
final dynamic data = json['data'];
if (data != null) {
baseRespModel.data = data;
}
return baseRespModel;
}
Map<String, dynamic> $BaseRespModelToJson(BaseRespModel entity) {
final Map<String, dynamic> data = <String, dynamic>{};
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;
}
}

View File

@ -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<String, dynamic> json) {
final WallpaperModel wallpaperModel = WallpaperModel();
final List<WallpaperData>? data = (json['data'] as List<dynamic>?)
?.map(
(e) => jsonConvert.convert<WallpaperData>(e) as WallpaperData)
.toList();
if (data != null) {
wallpaperModel.data = data;
}
final String? name = jsonConvert.convert<String>(json['name']);
if (name != null) {
wallpaperModel.name = name;
}
return wallpaperModel;
}
Map<String, dynamic> $WallpaperModelToJson(WallpaperModel entity) {
final Map<String, dynamic> data = <String, dynamic>{};
data['data'] = entity.data?.map((v) => v.toJson()).toList();
data['name'] = entity.name;
return data;
}
extension WallpaperModelExtension on WallpaperModel {
WallpaperModel copyWith({
List<WallpaperData>? data,
String? name,
}) {
return WallpaperModel()
..data = data ?? this.data
..name = name ?? this.name;
}
}
WallpaperData $WallpaperDataFromJson(Map<String, dynamic> json) {
final WallpaperData wallpaperData = WallpaperData();
final String? banner = jsonConvert.convert<String>(json['banner']);
if (banner != null) {
wallpaperData.banner = banner;
}
final String? original = jsonConvert.convert<String>(json['original']);
if (original != null) {
wallpaperData.original = original;
}
final String? previewThumb = jsonConvert.convert<String>(
json['previewThumb']);
if (previewThumb != null) {
wallpaperData.previewThumb = previewThumb;
}
final String? source = jsonConvert.convert<String>(json['source']);
if (source != null) {
wallpaperData.source = source;
}
return wallpaperData;
}
Map<String, dynamic> $WallpaperDataToJson(WallpaperData entity) {
final Map<String, dynamic> data = <String, dynamic>{};
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;
}
}

View File

@ -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(

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'about_controller.dart';
class AboutBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => AboutController());
}
}

View File

@ -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}';
}
}

View File

@ -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<AboutController> {
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,
),
),
);
}),
],
),
);
}
}

View File

@ -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<FavoriteController>();
var viewState = ViewState.loading;
late List<WallpaperData> 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();
}
}

View File

@ -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<FavoriteController> {
const FavoriteView({super.key});
@override
Widget build(BuildContext context) {
Get.lazyPut(() => FavoriteController());
return SafeArea(
child: GetBuilder<FavoriteController>(
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),
),
),
],
);
},
),
);
}
}

View File

@ -1,10 +0,0 @@
import 'package:get/get.dart';
import 'home_controller.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => HomeController());
}
}

View File

@ -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<WallpaperData> wallpaperList;
///
void initData(int index) {
WallpaperModel wallpaperModel = HomeController.to.getWallpaperData(index);
wallpaperList = wallpaperModel.data ?? <WallpaperData>[];
}
///
void itemOnTap(int index) async {
Get.toNamed(AppPages.wallpaperDet, arguments: {'position': index, 'wallpaperList': wallpaperList});
}
}

View File

@ -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<HomeClsController> {
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),
);
}
}

View File

@ -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<HomeController>();
late TabController tabController;
var wallpaperModelList = <WallpaperModel>[];
var clsPages = <HomeClsView>[];
@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<List<WallpaperModel>>(data) ?? <WallpaperModel>[];
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];
}
}

View File

@ -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<HomeController> {
const HomeView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Wallpaper'),
Get.lazyPut(() => HomeController());
return SafeArea(
child: GetBuilder<HomeController>(
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(),
),
);
}

View File

@ -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});
}
}
}

View File

@ -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<MeController> {
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),
],
),
),
),
);
}
}

View File

@ -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());
}
}

View File

@ -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<RootController>();
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);
}

View File

@ -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<RootController> {
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<BottomNavigationBarItem> _bottomNavigationBarItems() {
return controller.pages.map((e) {
return BottomNavigationBarItem(
icon: Icon(e.icons),
label: e.label,
);
}).toList();
}
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'wallpaper_det_controller.dart';
class WallpaperDetBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => WallpaperDetController());
}
}

View File

@ -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<WallpaperData> 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'] ?? <WallpaperData>[];
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 10Scoped 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>()) {
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);
}

View File

@ -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<WallpaperDetController> {
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(),
),
),
);
}
}

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'web_page_controller.dart';
class WebPageBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => WebPageController());
}
}

View File

@ -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));
}
}

View File

@ -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<WebPageController> {
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),
);
}),
);
}
}

View File

@ -0,0 +1,8 @@
// Author: fengshengxiong
// Date: 2024/5/7
// Description:
import 'package:flutter/material.dart';
///
const seedColor = Colors.black;

View File

@ -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;

View File

@ -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->5812
selectedLabelStyle: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w500),
selectedItemColor: Colors.white,
unselectedItemColor: const Color(0xFF757575),
),
);

View File

@ -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(),
),
];
}

View File

@ -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;
}
}

Some files were not shown because too many files have changed in this diff Show More