第一版完成
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/images/placeholder.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
12208
assets/json/wallpaper.json
Normal 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;
|
||||
|
||||
3
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
6
ios/Runner/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/Icon-App-1024x1024@1x.png
vendored
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
@ -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.
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
70
lib/common/components/base_appbar.dart
Normal 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);
|
||||
}
|
||||
56
lib/common/components/base_masonry_gridview.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
117
lib/common/components/dialog/hint_dialog.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
94
lib/common/components/dialog/process_dialog.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
22
lib/common/components/divider_widget.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
95
lib/common/components/get_bind_widget.dart
Normal file
@ -0,0 +1,95 @@
|
||||
// Author: fengshengxiong
|
||||
// Date: 2024/5/7
|
||||
// Description: 该组件可以回收GetXController,用于处理无法某些组件的GetXController无法回收的情况
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
/// GetBindWidget can bind GetxController, and when the page is disposed,
|
||||
/// it can automatically destroy the bound related GetXController
|
||||
///
|
||||
///
|
||||
/// Sample:
|
||||
///
|
||||
/// class SampleController extends GetxController {
|
||||
/// final String title = 'My Awesome View';
|
||||
/// }
|
||||
///
|
||||
/// class SamplePage extends StatelessWidget {
|
||||
/// final controller = SampleController();
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return GetBindWidget(
|
||||
/// bind: controller,
|
||||
/// child: Container(),
|
||||
/// );
|
||||
/// }
|
||||
/// }
|
||||
class GetBindWidget extends StatefulWidget {
|
||||
const GetBindWidget({
|
||||
super.key,
|
||||
this.bind,
|
||||
this.tag,
|
||||
this.binds,
|
||||
this.tags,
|
||||
required this.child,
|
||||
}) : assert(
|
||||
binds == null || tags == null || binds.length == tags.length,
|
||||
'The binds and tags arrays length should be equal\n'
|
||||
'and the elements in the two arrays correspond one-to-one',
|
||||
);
|
||||
|
||||
final GetxController? bind;
|
||||
final String? tag;
|
||||
|
||||
final List<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
lib/common/components/image_widget.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/common/components/keep_alive_wrapper.dart
Normal 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;
|
||||
}
|
||||
69
lib/common/components/title_bar_widget.dart
Normal 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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
75
lib/common/components/view_state_widget.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
80
lib/common/components/wallpaper_picker.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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/';
|
||||
}
|
||||
22
lib/common/models/base_resp_model.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
50
lib/common/models/wallpaper_model.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
49
lib/common/models/wallpaper_model.g.dart
Normal 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;
|
||||
}
|
||||
39
lib/common/network/base_error.dart
Normal 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;
|
||||
}
|
||||
169
lib/common/network/dio_client.dart
Normal 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;
|
||||
}
|
||||
31
lib/common/network/dio_interceptor.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
44
lib/common/storage/favorite_data.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
22
lib/common/storage/hive_storage.dart
Normal 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);
|
||||
}
|
||||
@ -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);
|
||||
@ -1,3 +0,0 @@
|
||||
// Author: fengshengxiong
|
||||
// Date: 2024/5/8
|
||||
// Description: 大小
|
||||
@ -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,
|
||||
);
|
||||
15
lib/common/utils/device_info_util.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
78
lib/common/utils/download_util.dart
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
70
lib/common/utils/filesize_util.dart
Normal 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
28
lib/common/utils/local_path_util.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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(
|
||||
28
lib/common/utils/num_util.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
// Description: 对象工具类
|
||||
|
||||
class ObjUtil {
|
||||
static bool isNotEmptyString(String? str) {
|
||||
static bool isNotEmptyStr(String? str) {
|
||||
return str == null || str.trim().isNotEmpty;
|
||||
}
|
||||
|
||||
145
lib/common/utils/permission_util.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
9
lib/generated/assets.dart
Normal 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';
|
||||
|
||||
}
|
||||
186
lib/generated/json/base/json_convert_content.dart
Normal 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];
|
||||
}
|
||||
}
|
||||
32
lib/generated/json/base/json_field.dart
Normal 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});
|
||||
}
|
||||
40
lib/generated/json/base_resp_model.g.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
84
lib/generated/json/wallpaper_model.g.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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(
|
||||
|
||||
10
lib/modules/about/about_binding.dart
Normal file
@ -0,0 +1,10 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'about_controller.dart';
|
||||
|
||||
class AboutBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => AboutController());
|
||||
}
|
||||
}
|
||||
19
lib/modules/about/about_controller.dart
Normal 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}';
|
||||
}
|
||||
}
|
||||
35
lib/modules/about/about_view.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
74
lib/modules/favorite/favorite_controller.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
37
lib/modules/favorite/favorite_view.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'home_controller.dart';
|
||||
|
||||
class HomeBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => HomeController());
|
||||
}
|
||||
}
|
||||
19
lib/modules/home/home_cls/home_cls_controller.dart
Normal 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});
|
||||
}
|
||||
}
|
||||
36
lib/modules/home/home_cls/home_cls_view.dart
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
16
lib/modules/me/me_controller.dart
Normal 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});
|
||||
}
|
||||
}
|
||||
}
|
||||
66
lib/modules/me/me_view.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
9
lib/modules/root/root_binding.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
46
lib/modules/root/root_controller.dart
Normal 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);
|
||||
}
|
||||
36
lib/modules/root/root_view.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
10
lib/modules/wallpaper_det/wallpaper_det_binding.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
105
lib/modules/wallpaper_det/wallpaper_det_controller.dart
Normal 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 10及以上版本,由于引入了分区存储(Scoped Storage)机制,如果应用只是需要访问媒体文件,而不是整个外部存储
|
||||
// ImageGallerySaver使用MediaStore API来添加图片到相册,无需请求存储权限
|
||||
// Android 10以下版本,需要申请存储权限
|
||||
int sdkVersion = await DeviceInfoUtil.getSDKVersion();
|
||||
if (sdkVersion < 29) {
|
||||
bool status = await PermissionUtil.checkPermission([Permission.storage]);
|
||||
canSave = status;
|
||||
}
|
||||
}
|
||||
if (canSave) {
|
||||
final result = await ImageGallerySaver.saveFile(savePath);
|
||||
if (result['isSuccess']) {
|
||||
toast('Saved to album');
|
||||
} else {
|
||||
toast('Unable to save to album');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (index == 1) {
|
||||
// 收藏
|
||||
WallpaperData wallpaperData = wallpaperList[position];
|
||||
if (FavoriteData().getWallpaperData().firstWhereOrNull((element) => element.original == wallpaperData.original) != null) {
|
||||
toast('Wallpaper has been collected');
|
||||
return;
|
||||
}
|
||||
FavoriteData().setWallpaperData(wallpaperData);
|
||||
toast('Wallpaper has been collected');
|
||||
// 刷新收藏列表
|
||||
if (Get.isRegistered<FavoriteController>()) {
|
||||
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);
|
||||
}
|
||||
95
lib/modules/wallpaper_det/wallpaper_det_view.dart
Normal 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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
10
lib/modules/web_page/web_page_binding.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
38
lib/modules/web_page/web_page_controller.dart
Normal 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));
|
||||
}
|
||||
}
|
||||
23
lib/modules/web_page/web_page_view.dart
Normal 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),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
8
lib/res/themes/app_colors.dart
Normal file
@ -0,0 +1,8 @@
|
||||
// Author: fengshengxiong
|
||||
// Date: 2024/5/7
|
||||
// Description: 颜色
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// 主要颜色
|
||||
const seedColor = Colors.black;
|
||||
9
lib/res/themes/app_sizes.dart
Normal 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;
|
||||
48
lib/res/themes/app_themes.dart
Normal 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->58,因此将大小改为12
|
||||
selectedLabelStyle: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w500),
|
||||
selectedItemColor: Colors.white,
|
||||
unselectedItemColor: const Color(0xFF757575),
|
||||
),
|
||||
);
|
||||
@ -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(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||