优化:应用设计图更改

This commit is contained in:
ivan 2026-03-11 09:45:42 +08:00
parent 35fb50040a
commit 1975e029e1
42 changed files with 1080 additions and 148 deletions

View File

@ -2,6 +2,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="com.android.vending.BILLING" />
<application <application
android:usesCleartextTraffic="${usesCleartextTraffic}" android:usesCleartextTraffic="${usesCleartextTraffic}"
android:label="PetsHero" android:label="PetsHero"

View File

@ -21,8 +21,8 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.9.1" apply false id "com.android.application" version "8.7.3" apply false
id "org.jetbrains.kotlin.android" version "1.9.22" apply false id "org.jetbrains.kotlin.android" version "2.1.0" apply false
} }
include ":app" include ":app"

View File

@ -0,0 +1,26 @@
# 设计图导出指南
`pencil-app-client.pen` 中的各页面导出为 PNG 图片。
## 导出步骤
1. 在 Pencil 中打开 `design/pencil-app-client.pen`
2. 在画布或图层面板中选中对应 Frame可按 Node ID 查找)
3. 右键 → **Export** → 选择 **PNG**
4. 保存到 `design/exports/` 目录,使用下方对应文件名
## 页面与 Node ID 对照表
| 页面 | Node ID | 导出文件名 |
|------|---------|------------|
| AI Video App (Home) | `bi8Au` | `home.png` |
| Gallery Screen | `hpwBg` | `gallery.png` |
| Profile Screen | `KXeow` | `profile.png` |
| Recharge Screen | `tPjdN` | `recharge.png` |
| Generate Video Screen | `mmLB5` | `generate_video.png` |
| Generate Video Progress Screen | `qGs6n` | `generate_progress.png` |
| Video Generation Result Screen | `cFA4T` | `result.png` |
## 快速查找 Node ID
在 Pencil 中选中 Frame 后,可在属性面板或图层信息中查看其 Node ID。

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

File diff suppressed because it is too large Load Diff

39
docs/adjuest.md Normal file
View File

@ -0,0 +1,39 @@
PetsHero AI-and
APP应用识别码2z2mly0afgqo
## 购买档位:
对应UI在充值页面 recharge_screen.dart
|事件名称|识别码|
|:------:|:-----:|
|19.99 |m0r9u9|
|49.99 |aht1ve|
|5.99 |47hhx9|
|9.99 |w775gd|
|99.99 |y7994m|
## 首日充值
用户注册当天的所有充值判定为首充
|事件名称|识别码|
|:------:|:-----:|
|first purchase |r6w4bi|
## 支付失败
|事件名称|识别码|
|:------:|:-----:|
|Order abnormal |4txlo1|
## 支付成功
如果是首日充值需要额外上报first purchase事件
|事件名称|识别码|
|:------:|:-----:|
|Purchase |ek780r|
## 注册
用户登录和注册调用的是同一个接口,当第一次调用的时候为注册事件
|事件名称|识别码|
|:------:|:-----:|
|register | 2z3dm4|
## 其他
|事件名称|识别码|
|:------:|:-----:|
|PetsHero AI Monthly VIP |96o5ez|
|PetsHero AI Weekly VIP |95yg4o|

View File

@ -30,6 +30,12 @@
@import image_picker_ios; @import image_picker_ios;
#endif #endif
#if __has_include(<shared_preferences_foundation/SharedPreferencesPlugin.h>)
#import <shared_preferences_foundation/SharedPreferencesPlugin.h>
#else
@import shared_preferences_foundation;
#endif
#if __has_include(<sqflite_darwin/SqflitePlugin.h>) #if __has_include(<sqflite_darwin/SqflitePlugin.h>)
#import <sqflite_darwin/SqflitePlugin.h> #import <sqflite_darwin/SqflitePlugin.h>
#else #else
@ -55,6 +61,7 @@
[FPPDeviceInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FPPDeviceInfoPlusPlugin"]]; [FPPDeviceInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FPPDeviceInfoPlusPlugin"]];
[GalPlugin registerWithRegistrar:[registry registrarForPlugin:@"GalPlugin"]]; [GalPlugin registerWithRegistrar:[registry registrarForPlugin:@"GalPlugin"]];
[FLTImagePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTImagePickerPlugin"]]; [FLTImagePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTImagePickerPlugin"]];
[SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
[SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]]; [SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]];
[FVPVideoPlayerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FVPVideoPlayerPlugin"]]; [FVPVideoPlayerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FVPVideoPlayerPlugin"]];
[VideoThumbnailPlugin registerWithRegistrar:[registry registrarForPlugin:@"VideoThumbnailPlugin"]]; [VideoThumbnailPlugin registerWithRegistrar:[registry registrarForPlugin:@"VideoThumbnailPlugin"]];

View File

@ -0,0 +1,112 @@
import 'package:adjust_sdk/adjust.dart';
import 'package:adjust_sdk/adjust_event.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Adjust docs/adjuest.md
abstract final class AdjustEvents {
//
static const String tier1999 = 'm0r9u9'; // 19.99
static const String tier4999 = 'aht1ve'; // 49.99
static const String tier599 = '47hhx9'; // 5.99
static const String tier999 = 'w775gd'; // 9.99
static const String tier9999 = 'y7994m'; // 99.99
//
static const String firstPurchase = 'r6w4bi';
//
static const String orderAbnormal = '4txlo1';
//
static const String purchase = 'ek780r';
// fast_login
static const String register = '2z3dm4';
//
static const String monthlyVip = '96o5ez'; // PetsHero AI Monthly VIP
static const String weeklyVip = '95yg4o'; // PetsHero AI Weekly VIP
/// 19.99, 9.99 token purchase
static String? tierTokenFromPrice(num price) {
final p = price.toStringAsFixed(2);
switch (p) {
case '5.99':
return tier599;
case '9.99':
return tier999;
case '19.99':
return tier1999;
case '49.99':
return tier4999;
case '99.99':
return tier9999;
default:
return purchase;
}
}
/// "¥19.99" / "\$19.99"
static num? parsePrice(String amount) {
final match = RegExp(r'[\d.]+').firstMatch(amount);
if (match == null) return null;
return num.tryParse(match.group(0)!);
}
static void _track(String eventToken) {
Adjust.trackEvent(AdjustEvent(eventToken));
}
///
static void trackTier(String eventToken) {
_track(eventToken);
}
///
static void trackFirstPurchase() {
_track(firstPurchase);
}
///
static void trackOrderAbnormal() {
_track(orderAbnormal);
}
/// trackFirstPurchase
static void trackPurchase() {
_track(purchase);
}
/// fast_login
static void trackRegister() {
_track(register);
}
/// PetsHero AI Monthly VIP
static void trackMonthlyVip() {
_track(monthlyVip);
}
/// PetsHero AI Weekly VIP
static void trackWeeklyVip() {
_track(weeklyVip);
}
static const String _keyRegisterDate = 'adjust_register_date';
/// Purchase first purchase
static Future<void> trackPurchaseSuccess() async {
_track(purchase);
final prefs = await SharedPreferences.getInstance();
final registerDate = prefs.getString(_keyRegisterDate);
if (registerDate != null &&
registerDate == DateTime.now().toIso8601String().substring(0, 10)) {
_track(firstPurchase);
}
}
///
static void trackPaymentFailed() {
_track(orderAbnormal);
}
}

View File

@ -4,7 +4,9 @@ import 'dart:convert';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../adjust/adjust_events.dart';
import '../api/api_client.dart'; import '../api/api_client.dart';
import '../api/proxy_client.dart'; import '../api/proxy_client.dart';
import '../api/services/user_api.dart'; import '../api/services/user_api.dart';
@ -108,6 +110,17 @@ class AuthService {
} else { } else {
_log('init: 响应中无 reevaluate (userToken)'); _log('init: 响应中无 reevaluate (userToken)');
} }
final prefs = await SharedPreferences.getInstance();
final hadLoggedIn = prefs.getBool('adjust_has_logged_in') ?? false;
if (!hadLoggedIn) {
AdjustEvents.trackRegister();
await prefs.setBool('adjust_has_logged_in', true);
await prefs.setString(
'adjust_register_date',
DateTime.now().toIso8601String().substring(0, 10),
);
_log('init: 首次登录,已上报 register');
}
final credits = data?['reveal'] as int?; final credits = data?['reveal'] as int?;
if (credits != null) { if (credits != null) {
UserState.setCredits(credits); UserState.setCredits(credits);

View File

@ -88,7 +88,7 @@ class _HomeScreenState extends State<HomeScreen> {
appBar: PreferredSize( appBar: PreferredSize(
preferredSize: const Size.fromHeight(56), preferredSize: const Size.fromHeight(56),
child: TopNavBar( child: TopNavBar(
title: 'AI Video', title: 'PetsHero AI',
credits: UserCreditsData.of(context)?.creditsDisplay ?? '--', credits: UserCreditsData.of(context)?.creditsDisplay ?? '--',
onCreditsTap: () => Navigator.of(context).pushNamed('/recharge'), onCreditsTap: () => Navigator.of(context).pushNamed('/recharge'),
), ),
@ -101,7 +101,9 @@ class _HomeScreenState extends State<HomeScreen> {
vertical: AppSpacing.xs, vertical: AppSpacing.xs,
), ),
child: _categoriesLoading child: _categoriesLoading
? const SizedBox(height: 40, child: Center(child: CircularProgressIndicator())) ? const SizedBox(
height: 40,
child: Center(child: CircularProgressIndicator()))
: HomeTabRow( : HomeTabRow(
categories: _categories, categories: _categories,
selectedId: _selectedCategory?.id ?? -1, selectedId: _selectedCategory?.id ?? -1,
@ -137,7 +139,8 @@ class _HomeScreenState extends State<HomeScreen> {
? task.credits480p.toString() ? task.credits480p.toString()
: '50'; : '50';
return VideoCard( return VideoCard(
imageUrl: task.previewImageUrl ?? _placeholderImage, imageUrl:
task.previewImageUrl ?? _placeholderImage,
videoUrl: task.previewVideoUrl, videoUrl: task.previewVideoUrl,
credits: credits, credits: credits,
isActive: _activeCardIndex == index, isActive: _activeCardIndex == index,
@ -162,5 +165,4 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
); );
} }
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:flutter_lucide/flutter_lucide.dart';
import '../../core/adjust/adjust_events.dart';
import '../../core/api/services/payment_api.dart'; import '../../core/api/services/payment_api.dart';
import '../../core/auth/auth_service.dart'; import '../../core/auth/auth_service.dart';
import '../../core/theme/app_colors.dart'; import '../../core/theme/app_colors.dart';
@ -80,7 +81,20 @@ class _RechargeScreenState extends State<RechargeScreen> {
} }
void _onBuy(ActivityItem item) { void _onBuy(ActivityItem item) {
// TODO: final price = AdjustEvents.parsePrice(item.actualAmount);
if (price != null) {
final tierToken = AdjustEvents.tierTokenFromPrice(price);
if (tierToken != null) {
AdjustEvents.trackTier(tierToken);
}
}
final titleLower = item.title.toLowerCase();
if (titleLower.contains('monthly vip')) {
AdjustEvents.trackMonthlyVip();
} else if (titleLower.contains('weekly vip')) {
AdjustEvents.trackWeeklyVip();
}
// TODO: AdjustEvents.trackPurchaseSuccess() AdjustEvents.trackPaymentFailed()
} }
@override @override

View File

@ -24,6 +24,7 @@ dependencies:
video_thumbnail: ^0.5.3 video_thumbnail: ^0.5.3
gal: ^2.3.0 gal: ^2.3.0
path_provider: ^2.1.2 path_provider: ^2.1.2
shared_preferences: ^2.2.2
flutter_cache_manager: ^3.3.1 flutter_cache_manager: ^3.3.1
dev_dependencies: dev_dependencies: