更新:启动图和logo,登录还没成功的时候显示加载指示器
BIN
android/app/src/main/res/drawable-hdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
android/app/src/main/res/drawable-mdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
android/app/src/main/res/drawable-night-hdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
android/app/src/main/res/drawable-night-mdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 346 KiB |
|
After Width: | Height: | Size: 601 KiB |
BIN
android/app/src/main/res/drawable-v21/background.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
BIN
android/app/src/main/res/drawable-xhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 346 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/android12splash.png
Normal file
|
After Width: | Height: | Size: 601 KiB |
BIN
android/app/src/main/res/drawable/background.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
6
android/app/src/main/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 94 KiB |
22
android/app/src/main/res/values-night-v31/styles.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#0a0a12</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -1,7 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
|
||||
22
android/app/src/main/res/values-v31/styles.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#0a0a12</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -1,7 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
|
||||
BIN
assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 603 KiB |
BIN
assets/images/splash.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 893 KiB |
|
Before Width: | Height: | Size: 843 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 858 KiB |
BIN
design/images/generated-1773675899343.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
@ -4082,10 +4082,12 @@
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "OKMsh",
|
||||
"name": "navSpacer",
|
||||
"id": "RGAdu",
|
||||
"name": "reportBtn",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
"height": 40,
|
||||
"justifyContent": "center",
|
||||
"alignItems": "center"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -4117,26 +4119,102 @@
|
||||
},
|
||||
"cornerRadius": 16,
|
||||
"layout": "vertical",
|
||||
"justifyContent": "center",
|
||||
"justifyContent": "space_between",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "a3uQs",
|
||||
"width": 72,
|
||||
"height": 72,
|
||||
"iconFontName": "play",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "#FFFFFF80"
|
||||
"type": "frame",
|
||||
"id": "Ne80z",
|
||||
"name": "topRow",
|
||||
"width": "fill_container",
|
||||
"height": 40,
|
||||
"padding": [
|
||||
16,
|
||||
20,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"justifyContent": "end",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "h2IK3",
|
||||
"name": "reportBtn",
|
||||
"width": 44,
|
||||
"height": 44,
|
||||
"fill": "#00000099",
|
||||
"cornerRadius": 8,
|
||||
"justifyContent": "center",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "GtWHh",
|
||||
"width": 24,
|
||||
"height": 24,
|
||||
"iconFontName": "triangle-alert",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "SuXL2",
|
||||
"fill": "#FFFFFF99",
|
||||
"content": "Your video is ready",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "500"
|
||||
"type": "frame",
|
||||
"id": "wD2sT",
|
||||
"name": "centerGroup",
|
||||
"width": "fill_container",
|
||||
"layout": "vertical",
|
||||
"gap": 8,
|
||||
"justifyContent": "center",
|
||||
"alignItems": "center",
|
||||
"children": [
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "a3uQs",
|
||||
"enabled": false,
|
||||
"width": 72,
|
||||
"height": 72,
|
||||
"iconFontName": "play",
|
||||
"iconFontFamily": "lucide",
|
||||
"fill": "#FFFFFF80"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "SuXL2",
|
||||
"fill": "#FFFFFF99",
|
||||
"content": "your work is ready",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 14,
|
||||
"fontWeight": "500"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "nNSbD",
|
||||
"name": "watermarkRow",
|
||||
"width": "fill_container",
|
||||
"padding": [
|
||||
0,
|
||||
20,
|
||||
20,
|
||||
0
|
||||
],
|
||||
"justifyContent": "end",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "BqSHo",
|
||||
"fill": "#FFFFFF99",
|
||||
"content": "PetsHero AI",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "500"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
// 下面的三个字段说明:A面都是 false,B面都是 true
|
||||
"need_wait": false, // 是否展示 Video 菜单
|
||||
"safe_area": false, // 是否防止截屏
|
||||
"safe_area": false, // 是否防止截屏;为 true 时启用系统截屏/录屏防护(Android FLAG_SECURE,iOS 检测)
|
||||
"lucky": false, // 是否显示第三方支付
|
||||
"privacy": "https://www.petsheroai.xyz/privacy.html",
|
||||
"agreement": "https://www.petsheroai.xyz/terms.html",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1014 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 63 KiB |
21
ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "background.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png
vendored
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/background@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
ios/Runner/Assets.xcassets/LaunchBackground.imageset/background@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 247 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "LaunchImage.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 69 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 69 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 69 B |
38
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,38 @@
|
||||
<?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">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.04" green="0.04" blue="0.07" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
|
||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
|
||||
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
|
||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchBackground" width="375" height="812"/>
|
||||
</resources>
|
||||
</document>
|
||||
@ -18,6 +18,12 @@
|
||||
@import device_info_plus;
|
||||
#endif
|
||||
|
||||
#if __has_include(<flutter_native_splash/FlutterNativeSplashPlugin.h>)
|
||||
#import <flutter_native_splash/FlutterNativeSplashPlugin.h>
|
||||
#else
|
||||
@import flutter_native_splash;
|
||||
#endif
|
||||
|
||||
#if __has_include(<gal/GalPlugin.h>)
|
||||
#import <gal/GalPlugin.h>
|
||||
#else
|
||||
@ -36,6 +42,12 @@
|
||||
@import in_app_purchase_storekit;
|
||||
#endif
|
||||
|
||||
#if __has_include(<screen_secure/ScreenSecurePlugin.h>)
|
||||
#import <screen_secure/ScreenSecurePlugin.h>
|
||||
#else
|
||||
@import screen_secure;
|
||||
#endif
|
||||
|
||||
#if __has_include(<shared_preferences_foundation/SharedPreferencesPlugin.h>)
|
||||
#import <shared_preferences_foundation/SharedPreferencesPlugin.h>
|
||||
#else
|
||||
@ -77,9 +89,11 @@
|
||||
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
|
||||
[AdjustSdk registerWithRegistrar:[registry registrarForPlugin:@"AdjustSdk"]];
|
||||
[FPPDeviceInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FPPDeviceInfoPlusPlugin"]];
|
||||
[FlutterNativeSplashPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterNativeSplashPlugin"]];
|
||||
[GalPlugin registerWithRegistrar:[registry registrarForPlugin:@"GalPlugin"]];
|
||||
[FLTImagePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTImagePickerPlugin"]];
|
||||
[InAppPurchasePlugin registerWithRegistrar:[registry registrarForPlugin:@"InAppPurchasePlugin"]];
|
||||
[ScreenSecurePlugin registerWithRegistrar:[registry registrarForPlugin:@"ScreenSecurePlugin"]];
|
||||
[SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
|
||||
[SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]];
|
||||
[URLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"URLLauncherPlugin"]];
|
||||
|
||||
@ -1,51 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>PetsHero AI</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>We need access to your photos to use as reference for AI image generation.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>We need permission to save videos and images to your photo library.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>We need camera access to capture reference images for AI generation.</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>PetsHero AI</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>PetsHero AI</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>We need access to your photos to use as reference for AI image generation.</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>We need permission to save videos and images to your photo library.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>We need camera access to capture reference images for AI generation.</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>PetsHero AI</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
39
lib/app.dart
@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'core/auth/auth_service.dart';
|
||||
import 'core/theme/app_colors.dart';
|
||||
import 'core/theme/app_theme.dart';
|
||||
import 'core/user/user_state.dart';
|
||||
import 'features/gallery/gallery_screen.dart';
|
||||
@ -36,12 +38,37 @@ class _AppState extends State<App> {
|
||||
debugShowCheckedModeBanner: false,
|
||||
initialRoute: '/',
|
||||
builder: (context, child) {
|
||||
return SafeArea(
|
||||
top: true,
|
||||
left: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
SafeArea(
|
||||
top: true,
|
||||
left: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
child: child ?? const SizedBox.shrink(),
|
||||
),
|
||||
FutureBuilder<void>(
|
||||
future: AuthService.loginComplete,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Positioned.fill(
|
||||
child: AbsorbPointer(
|
||||
child: Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
routes: {
|
||||
|
||||
@ -12,7 +12,8 @@ abstract final class ApiConfig {
|
||||
static const String packageName = 'com.petsheroai.app';
|
||||
|
||||
/// 预发环境域名
|
||||
static const String preBaseUrl = 'https://pre-ai.petsheroai.xyz';
|
||||
static const String preBaseUrl =
|
||||
'https://ai.petsheroai.xyz'; //'https://pre-ai.petsheroai.xyz';
|
||||
|
||||
/// 生产环境域名
|
||||
static const String prodBaseUrl = 'https://ai.petsheroai.xyz';
|
||||
|
||||
@ -4,6 +4,7 @@ import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:screen_secure/screen_secure.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../adjust/adjust_events.dart';
|
||||
@ -23,6 +24,9 @@ class AuthService {
|
||||
|
||||
static Future<void>? _loginFuture;
|
||||
|
||||
/// 登录是否已完成,用于 UI 控制(如登录中隐藏页面加载指示器)
|
||||
static final ValueNotifier<bool> isLoginComplete = ValueNotifier(false);
|
||||
|
||||
/// 登录完成后的 Future,需鉴权接口应 await 此 Future 再请求
|
||||
static Future<void> get loginComplete => _loginFuture ?? Future<void>.value();
|
||||
|
||||
@ -52,6 +56,28 @@ class AuthService {
|
||||
return digest.toString().toUpperCase();
|
||||
}
|
||||
|
||||
/// 根据 extConfig.safe_area 启用/禁用系统截屏防护
|
||||
static Future<void> _applyScreenSecure(bool? safeArea) async {
|
||||
if (defaultTargetPlatform != TargetPlatform.android &&
|
||||
defaultTargetPlatform != TargetPlatform.iOS) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ScreenSecure.init(screenshotBlock: false, screenRecordBlock: false);
|
||||
if (safeArea == true) {
|
||||
await ScreenSecure.enableScreenshotBlock();
|
||||
await ScreenSecure.enableScreenRecordBlock();
|
||||
_logMsg('safe_area=true: 已启用截屏/录屏防护');
|
||||
} else {
|
||||
await ScreenSecure.disableScreenshotBlock();
|
||||
await ScreenSecure.disableScreenRecordBlock();
|
||||
_logMsg('safe_area=$safeArea: 已关闭截屏/录屏防护');
|
||||
}
|
||||
} on ScreenSecureException catch (e) {
|
||||
_logMsg('ScreenSecure 设置失败: ${e.message}');
|
||||
}
|
||||
}
|
||||
|
||||
/// 将 common_info 响应保存到全局,并解析 surge 中的 lucky(是否开启三方支付)
|
||||
static void _saveCommonInfoToState(Map<String, dynamic> data) {
|
||||
final reveal = data['reveal'] as int?;
|
||||
@ -70,13 +96,16 @@ class AuthService {
|
||||
if (surge != null) {
|
||||
final enable = surge['lucky'] as bool?;
|
||||
UserState.setEnableThirdPartyPayment(enable);
|
||||
// extConfig:need_wait = 是否展示 Video 菜单,items = 图片列表(见 docs/extConfig.md)
|
||||
// extConfig:need_wait = 是否展示 Video 菜单,safe_area = 是否防止截屏,items = 图片列表(见 docs/extConfig.md)
|
||||
final needWait = surge['need_wait'] as bool?;
|
||||
final safeArea = surge['safe_area'] as bool?;
|
||||
final items = surge['items'] as List<dynamic>?;
|
||||
UserState.setExtConfig(
|
||||
needShowVideoMenuValue: needWait,
|
||||
safeAreaValue: safeArea,
|
||||
items: items,
|
||||
);
|
||||
_applyScreenSecure(safeArea);
|
||||
}
|
||||
} catch (e) {
|
||||
_logMsg('surge JSON 解析失败: $e');
|
||||
@ -224,7 +253,10 @@ class AuthService {
|
||||
_logMsg('init: 异常 $e');
|
||||
_logMsg('init: 堆栈 $st');
|
||||
} finally {
|
||||
if (!completer.isCompleted) completer.complete();
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
isLoginComplete.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,8 @@ class UserState {
|
||||
|
||||
/// 是否展示 Video 分类栏(来自 common_info surge.need_wait,见 docs/extConfig.md)
|
||||
static final ValueNotifier<bool?> needShowVideoMenu = ValueNotifier<bool?>(null);
|
||||
/// 是否防止截屏(来自 common_info surge.safe_area,见 docs/extConfig.md)
|
||||
static final ValueNotifier<bool?> safeArea = ValueNotifier<bool?>(null);
|
||||
/// extConfig.items 图片列表(来自 common_info surge.items)
|
||||
static final ValueNotifier<List<dynamic>?> extConfigItems = ValueNotifier<List<dynamic>?>(null);
|
||||
|
||||
@ -43,8 +45,13 @@ class UserState {
|
||||
enableThirdPartyPayment.value = value;
|
||||
}
|
||||
|
||||
static void setExtConfig({bool? needShowVideoMenuValue, List<dynamic>? items}) {
|
||||
static void setExtConfig({
|
||||
bool? needShowVideoMenuValue,
|
||||
bool? safeAreaValue,
|
||||
List<dynamic>? items,
|
||||
}) {
|
||||
if (needShowVideoMenuValue != null) needShowVideoMenu.value = needShowVideoMenuValue;
|
||||
if (safeAreaValue != null) safeArea.value = safeAreaValue;
|
||||
if (items != null) extConfigItems.value = items;
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ import '../../features/gallery/models/gallery_task_item.dart';
|
||||
import '../../shared/tab_selector_scope.dart';
|
||||
import '../../shared/widgets/bottom_nav_bar.dart';
|
||||
|
||||
/// Progress states: 1=队列中 2=处理中 3=完成 4=超时 5=错误 6=中止
|
||||
/// Progress states: 1=Queued 2=Processing 3=Completed 4=Timeout 5=Error 6=Aborted
|
||||
/// Progress bar has 3 stages; states 3–6 are stage 3.
|
||||
const _stateLabels = <int, String>{
|
||||
1: 'Queued',
|
||||
@ -72,6 +72,7 @@ class GenerateProgressScreen extends StatefulWidget {
|
||||
class _GenerateProgressScreenState extends State<GenerateProgressScreen> {
|
||||
int? _state;
|
||||
Timer? _pollTimer;
|
||||
bool _isFetching = false;
|
||||
|
||||
double get _progress => _progressForState(_state);
|
||||
|
||||
@ -104,7 +105,7 @@ class _GenerateProgressScreenState extends State<GenerateProgressScreen> {
|
||||
Future<void> _startPolling() async {
|
||||
await AuthService.loginComplete;
|
||||
_pollTimer = Timer.periodic(
|
||||
const Duration(seconds: 1),
|
||||
const Duration(seconds: 5),
|
||||
(_) => _fetchProgress(),
|
||||
);
|
||||
_fetchProgress();
|
||||
@ -112,7 +113,9 @@ class _GenerateProgressScreenState extends State<GenerateProgressScreen> {
|
||||
|
||||
Future<void> _fetchProgress() async {
|
||||
if (widget.taskId == null) return;
|
||||
if (_isFetching) return;
|
||||
|
||||
_isFetching = true;
|
||||
try {
|
||||
final res = await ImageApi.getProgress(
|
||||
sentinel: ApiConfig.appId,
|
||||
@ -163,11 +166,14 @@ class _GenerateProgressScreenState extends State<GenerateProgressScreen> {
|
||||
break;
|
||||
}
|
||||
} catch (_) {}
|
||||
finally {
|
||||
_isFetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelText = _stateLabels[_state] ?? '队列中';
|
||||
final labelText = _stateLabels[_state] ?? 'Queued';
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
@ -250,7 +256,7 @@ class _VideoPreview extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Progress bar: 3 stages. Label Eghqc shows state (队列中|处理中|完成|超时|错误|中止)
|
||||
/// Progress bar: 3 stages. Label shows state (Queued|Processing|Completed|Timeout|Error|Aborted)
|
||||
class _ProgressSection extends StatelessWidget {
|
||||
const _ProgressSection({required this.progress, required this.label});
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_lucide/flutter_lucide.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
@ -179,12 +178,13 @@ class _GenerateVideoScreenState extends State<GenerateVideoScreen> {
|
||||
throw Exception('User not logged in');
|
||||
}
|
||||
|
||||
final templateName = widget.task?.templateName ?? '';
|
||||
final createRes = await ImageApi.createTask(
|
||||
asset: userId,
|
||||
guild: filePath,
|
||||
allowance: false,
|
||||
cipher: widget.task?.taskType ?? '',
|
||||
congregation: widget.task?.templateName ?? '',
|
||||
congregation: templateName == 'BananaTask' ? null : templateName,
|
||||
heatmap: _heatmap,
|
||||
ext: widget.task?.ext,
|
||||
);
|
||||
|
||||
@ -192,35 +192,74 @@ class _MediaDisplay extends StatelessWidget {
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: videoUrl != null && videoController != null
|
||||
? _VideoPlayer(
|
||||
controller: videoController!,
|
||||
)
|
||||
: videoUrl != null && videoLoading
|
||||
? Container(
|
||||
color: AppColors.textPrimary,
|
||||
alignment: Alignment.center,
|
||||
child: const CircularProgressIndicator(
|
||||
color: AppColors.surface,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
videoUrl != null && videoController != null
|
||||
? _VideoPlayer(
|
||||
controller: videoController!,
|
||||
)
|
||||
: videoUrl != null && videoLoadError != null
|
||||
: videoUrl != null && videoLoading
|
||||
? Container(
|
||||
color: AppColors.textPrimary,
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'Load failed',
|
||||
style: TextStyle(color: AppColors.surface),
|
||||
child: const CircularProgressIndicator(
|
||||
color: AppColors.surface,
|
||||
),
|
||||
)
|
||||
: imageUrl != null
|
||||
? CachedNetworkImage(
|
||||
imageUrl: imageUrl!,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (_, __) => _Placeholder(),
|
||||
errorWidget: (_, __, ___) => _Placeholder(),
|
||||
: videoUrl != null && videoLoadError != null
|
||||
? Container(
|
||||
color: AppColors.textPrimary,
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'Load failed',
|
||||
style: TextStyle(color: AppColors.surface),
|
||||
),
|
||||
)
|
||||
: _Placeholder(),
|
||||
: imageUrl != null
|
||||
? CachedNetworkImage(
|
||||
imageUrl: imageUrl!,
|
||||
fit: BoxFit.cover,
|
||||
placeholder: (_, __) => _Placeholder(),
|
||||
errorWidget: (_, __, ___) => _Placeholder(),
|
||||
)
|
||||
: _Placeholder(),
|
||||
Positioned(
|
||||
top: 16,
|
||||
right: 20,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// TODO: Report action
|
||||
},
|
||||
child: Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.6),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
LucideIcons.triangle_alert,
|
||||
size: 24,
|
||||
color: AppColors.surface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
child: Text(
|
||||
'PetsHero AI',
|
||||
style: AppTypography.bodyMedium.copyWith(
|
||||
color: AppColors.surface.withValues(alpha: 0.6),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -263,49 +302,26 @@ class _VideoPlayerState extends State<_VideoPlayer> {
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: SizedBox(
|
||||
width: widget.controller.value.size.width > 0
|
||||
? widget.controller.value.size.width
|
||||
: 16,
|
||||
height: widget.controller.value.size.height > 0
|
||||
? widget.controller.value.size.height
|
||||
: 9,
|
||||
child: VideoPlayer(widget.controller),
|
||||
),
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.controller.value.isPlaying) {
|
||||
widget.controller.pause();
|
||||
} else {
|
||||
widget.controller.play();
|
||||
}
|
||||
},
|
||||
child: FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: SizedBox(
|
||||
width: widget.controller.value.size.width > 0
|
||||
? widget.controller.value.size.width
|
||||
: 16,
|
||||
height: widget.controller.value.size.height > 0
|
||||
? widget.controller.value.size.height
|
||||
: 9,
|
||||
child: VideoPlayer(widget.controller),
|
||||
),
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.controller.value.isPlaying) {
|
||||
widget.controller.pause();
|
||||
} else {
|
||||
widget.controller.play();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface.withValues(alpha: 0.8),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
widget.controller.value.isPlaying
|
||||
? LucideIcons.pause
|
||||
: LucideIcons.play,
|
||||
size: 32,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -315,22 +331,12 @@ class _Placeholder extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: AppColors.textPrimary,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
LucideIcons.play,
|
||||
size: 72,
|
||||
color: AppColors.surface.withValues(alpha: 0.5),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Text(
|
||||
'Your work is ready',
|
||||
style: AppTypography.bodyRegular.copyWith(
|
||||
color: AppColors.surface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'Your work is ready',
|
||||
style: AppTypography.bodyRegular.copyWith(
|
||||
color: AppColors.surface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
super.initState();
|
||||
UserState.needShowVideoMenu.addListener(_onExtConfigChanged);
|
||||
UserState.extConfigItems.addListener(_onExtConfigChanged);
|
||||
AuthService.isLoginComplete.addListener(_onExtConfigChanged);
|
||||
_loadCategories();
|
||||
if (widget.isActive) refreshAccount();
|
||||
}
|
||||
@ -46,6 +47,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
void dispose() {
|
||||
UserState.needShowVideoMenu.removeListener(_onExtConfigChanged);
|
||||
UserState.extConfigItems.removeListener(_onExtConfigChanged);
|
||||
AuthService.isLoginComplete.removeListener(_onExtConfigChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -75,6 +77,24 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 是否处于首次加载中(分类/任务/extConfig 尚未就绪)
|
||||
/// 登录未完成时不显示页面加载指示器,由登录遮罩负责
|
||||
bool get _isListLoading {
|
||||
if (!AuthService.isLoginComplete.value) return false;
|
||||
if (_showVideoMenu) {
|
||||
if (_categoriesLoading) return true;
|
||||
if (_selectedCategory?.id == kExtCategoryId) {
|
||||
return UserState.extConfigItems.value == null;
|
||||
}
|
||||
return _tasksLoading;
|
||||
}
|
||||
return UserState.extConfigItems.value == null;
|
||||
}
|
||||
|
||||
/// 分类栏加载中时是否显示加载指示器(登录未完成时不显示)
|
||||
bool get _showCategoriesLoading =>
|
||||
AuthService.isLoginComplete.value && _categoriesLoading;
|
||||
|
||||
/// 当前列表:need_wait false 时用 extConfig.items;true 且选中固定分类时用 extConfig.items;否则用 _tasks
|
||||
List<TaskItem> get _displayTasks {
|
||||
if (!_showVideoMenu) {
|
||||
@ -187,7 +207,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
horizontal: AppSpacing.screenPadding,
|
||||
vertical: AppSpacing.xs,
|
||||
),
|
||||
child: _categoriesLoading
|
||||
child: _showCategoriesLoading
|
||||
? const SizedBox(
|
||||
height: 40,
|
||||
child: Center(child: CircularProgressIndicator()))
|
||||
@ -198,9 +218,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _showVideoMenu &&
|
||||
_selectedCategory?.id != kExtCategoryId &&
|
||||
_tasksLoading
|
||||
child: _isListLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
|
||||
@ -23,9 +23,9 @@ void main() async {
|
||||
statusBarBrightness: Brightness.light,
|
||||
),
|
||||
);
|
||||
runApp(const App());
|
||||
// APP 打开时后台执行快速登录
|
||||
// 先启动登录,确保首次构建时 loginComplete 可被监听
|
||||
AuthService.init();
|
||||
runApp(const App());
|
||||
// 尽早订阅 purchaseStream,否则未确认订单不会出现在 queryPastPurchases 中,补单会为空
|
||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||
GooglePlayPurchaseService.startPendingPurchaseListener();
|
||||
|
||||
19
pubspec.yaml
@ -1,7 +1,7 @@
|
||||
name: pets_hero_ai
|
||||
description: PetsHero AI Application.
|
||||
publish_to: 'none'
|
||||
version: 1.0.4+5
|
||||
version: 1.0.5+6
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
@ -30,18 +30,31 @@ dependencies:
|
||||
url_launcher: ^6.2.5
|
||||
in_app_purchase: ^3.2.0
|
||||
webview_flutter: ^4.10.0
|
||||
screen_secure: ^1.0.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
flutter_launcher_icons: ^0.14.4
|
||||
flutter_native_splash: ^2.4.7
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
ios: true
|
||||
image_path: "design/images/generated-1773069485332.png"
|
||||
ios: false # iOS xcodeproj not present; run with ios:true after adding iOS project
|
||||
image_path: "assets/images/logo.png"
|
||||
remove_alpha_ios: true
|
||||
|
||||
flutter_native_splash:
|
||||
background_image: "assets/images/splash.png"
|
||||
android: true
|
||||
ios: true
|
||||
# Android 12+ 使用不同 API,不支持全屏背景图,需单独配置
|
||||
android_12:
|
||||
color: "#0a0a12"
|
||||
image: "assets/images/logo.png"
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/images/
|
||||
|
||||