优化:跑通初步流程

This commit is contained in:
ivan 2026-03-26 10:39:39 +08:00
parent 7b8ab4936d
commit c6449734f9
32 changed files with 5501 additions and 189 deletions

View File

@ -1,6 +1,12 @@
{ {
"configVersion": 2, "configVersion": 2,
"packages": [ "packages": [
{
"name": "adjust_sdk",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/adjust_sdk-5.5.1",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{ {
"name": "args", "name": "args",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/args-2.7.0", "rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/args-2.7.0",
@ -55,12 +61,36 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "2.18" "languageVersion": "2.18"
}, },
{
"name": "facebook_app_events",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/facebook_app_events-0.26.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "ffi",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/ffi-2.2.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "file",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/file-7.0.1",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{ {
"name": "flutter", "name": "flutter",
"rootUri": "file:///Users/sven/flutter/packages/flutter", "rootUri": "file:///Users/sven/flutter/packages/flutter",
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.9" "languageVersion": "3.9"
}, },
{
"name": "flutter_web_plugins",
"rootUri": "file:///Users/sven/flutter/packages/flutter_web_plugins",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{ {
"name": "http", "name": "http",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/http-1.6.0", "rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/http-1.6.0",
@ -73,12 +103,42 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.4" "languageVersion": "3.4"
}, },
{
"name": "in_app_purchase",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/in_app_purchase-3.2.3",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "in_app_purchase_android",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/in_app_purchase_android-0.4.0+8",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "in_app_purchase_platform_interface",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/in_app_purchase_platform_interface-1.4.0",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "in_app_purchase_storekit",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/in_app_purchase_storekit-0.4.8+1",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{ {
"name": "js", "name": "js",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/js-0.7.2", "rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/js-0.7.2",
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.7" "languageVersion": "3.7"
}, },
{
"name": "json_annotation",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/json_annotation-4.11.0",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{ {
"name": "logger", "name": "logger",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/logger-2.7.0", "rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/logger-2.7.0",
@ -103,12 +163,90 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.4" "languageVersion": "3.4"
}, },
{
"name": "path_provider_linux",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1",
"packageUri": "lib/",
"languageVersion": "2.19"
},
{
"name": "path_provider_platform_interface",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/path_provider_platform_interface-2.1.2",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "path_provider_windows",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "platform",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/platform-3.1.6",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "play_install_referrer",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/play_install_referrer-0.5.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "plugin_platform_interface",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{ {
"name": "pointycastle", "name": "pointycastle",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/pointycastle-3.9.1", "rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/pointycastle-3.9.1",
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.2" "languageVersion": "3.2"
}, },
{
"name": "shared_preferences",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/shared_preferences-2.5.4",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "shared_preferences_android",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.21",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "shared_preferences_foundation",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "shared_preferences_linux",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "shared_preferences_platform_interface",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_platform_interface-2.4.1",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "shared_preferences_web",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "shared_preferences_windows",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{ {
"name": "sky_engine", "name": "sky_engine",
"rootUri": "file:///Users/sven/flutter/bin/cache/pkg/sky_engine", "rootUri": "file:///Users/sven/flutter/bin/cache/pkg/sky_engine",
@ -151,6 +289,12 @@
"packageUri": "lib/", "packageUri": "lib/",
"languageVersion": "3.4" "languageVersion": "3.4"
}, },
{
"name": "xdg_directories",
"rootUri": "file:///Users/sven/.pub-cache/hosted/pub.dev/xdg_directories-1.1.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{ {
"name": "client_proxy_framework", "name": "client_proxy_framework",
"rootUri": "../", "rootUri": "../",

View File

@ -7,14 +7,51 @@
"name": "client_proxy_framework", "name": "client_proxy_framework",
"version": "1.0.0", "version": "1.0.0",
"dependencies": [ "dependencies": [
"adjust_sdk",
"crypto", "crypto",
"encrypt", "encrypt",
"facebook_app_events",
"flutter", "flutter",
"http", "http",
"logger" "in_app_purchase",
"in_app_purchase_android",
"logger",
"play_install_referrer",
"shared_preferences"
], ],
"devDependencies": [] "devDependencies": []
}, },
{
"name": "play_install_referrer",
"version": "0.5.0",
"dependencies": [
"flutter"
]
},
{
"name": "in_app_purchase_android",
"version": "0.4.0+8",
"dependencies": [
"collection",
"flutter",
"in_app_purchase_platform_interface"
]
},
{
"name": "facebook_app_events",
"version": "0.26.0",
"dependencies": [
"flutter"
]
},
{
"name": "adjust_sdk",
"version": "5.5.1",
"dependencies": [
"flutter",
"meta"
]
},
{ {
"name": "logger", "name": "logger",
"version": "2.7.0", "version": "2.7.0",
@ -65,8 +102,16 @@
] ]
}, },
{ {
"name": "clock", "name": "in_app_purchase_platform_interface",
"version": "1.1.2", "version": "1.4.0",
"dependencies": [
"flutter",
"plugin_platform_interface"
]
},
{
"name": "collection",
"version": "1.19.1",
"dependencies": [] "dependencies": []
}, },
{ {
@ -74,6 +119,11 @@
"version": "1.17.0", "version": "1.17.0",
"dependencies": [] "dependencies": []
}, },
{
"name": "clock",
"version": "1.1.2",
"dependencies": []
},
{ {
"name": "typed_data", "name": "typed_data",
"version": "1.4.0", "version": "1.4.0",
@ -90,11 +140,6 @@
"js" "js"
] ]
}, },
{
"name": "collection",
"version": "1.19.1",
"dependencies": []
},
{ {
"name": "asn1lib", "name": "asn1lib",
"version": "1.6.5", "version": "1.6.5",
@ -187,6 +232,174 @@
"name": "path", "name": "path",
"version": "1.9.1", "version": "1.9.1",
"dependencies": [] "dependencies": []
},
{
"name": "plugin_platform_interface",
"version": "2.1.8",
"dependencies": [
"meta"
]
},
{
"name": "in_app_purchase",
"version": "3.2.3",
"dependencies": [
"flutter",
"in_app_purchase_android",
"in_app_purchase_platform_interface",
"in_app_purchase_storekit"
]
},
{
"name": "in_app_purchase_storekit",
"version": "0.4.8+1",
"dependencies": [
"collection",
"flutter",
"in_app_purchase_platform_interface",
"json_annotation"
]
},
{
"name": "json_annotation",
"version": "4.11.0",
"dependencies": [
"meta"
]
},
{
"name": "shared_preferences",
"version": "2.5.4",
"dependencies": [
"flutter",
"shared_preferences_android",
"shared_preferences_foundation",
"shared_preferences_linux",
"shared_preferences_platform_interface",
"shared_preferences_web",
"shared_preferences_windows"
]
},
{
"name": "shared_preferences_windows",
"version": "2.4.1",
"dependencies": [
"file",
"flutter",
"path",
"path_provider_platform_interface",
"path_provider_windows",
"shared_preferences_platform_interface"
]
},
{
"name": "shared_preferences_platform_interface",
"version": "2.4.1",
"dependencies": [
"flutter",
"plugin_platform_interface"
]
},
{
"name": "shared_preferences_linux",
"version": "2.4.1",
"dependencies": [
"file",
"flutter",
"path",
"path_provider_linux",
"path_provider_platform_interface",
"shared_preferences_platform_interface"
]
},
{
"name": "shared_preferences_web",
"version": "2.4.3",
"dependencies": [
"flutter",
"flutter_web_plugins",
"shared_preferences_platform_interface",
"web"
]
},
{
"name": "flutter_web_plugins",
"version": "0.0.0",
"dependencies": [
"flutter"
]
},
{
"name": "shared_preferences_foundation",
"version": "2.5.6",
"dependencies": [
"flutter",
"shared_preferences_platform_interface"
]
},
{
"name": "file",
"version": "7.0.1",
"dependencies": [
"meta",
"path"
]
},
{
"name": "path_provider_platform_interface",
"version": "2.1.2",
"dependencies": [
"flutter",
"platform",
"plugin_platform_interface"
]
},
{
"name": "platform",
"version": "3.1.6",
"dependencies": []
},
{
"name": "path_provider_linux",
"version": "2.2.1",
"dependencies": [
"ffi",
"flutter",
"path",
"path_provider_platform_interface",
"xdg_directories"
]
},
{
"name": "xdg_directories",
"version": "1.1.0",
"dependencies": [
"meta",
"path"
]
},
{
"name": "ffi",
"version": "2.2.0",
"dependencies": []
},
{
"name": "path_provider_windows",
"version": "2.3.0",
"dependencies": [
"ffi",
"flutter",
"path",
"path_provider_platform_interface"
]
},
{
"name": "shared_preferences_android",
"version": "2.4.21",
"dependencies": [
"flutter",
"shared_preferences_platform_interface"
]
} }
], ],
"configVersion": 1 "configVersion": 1

View File

@ -0,0 +1 @@
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"adjust_sdk","path":"/Users/sven/.pub-cache/hosted/pub.dev/adjust_sdk-5.5.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"facebook_app_events","path":"/Users/sven/.pub-cache/hosted/pub.dev/facebook_app_events-0.26.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"in_app_purchase_storekit","path":"/Users/sven/.pub-cache/hosted/pub.dev/in_app_purchase_storekit-0.4.8+1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"adjust_sdk","path":"/Users/sven/.pub-cache/hosted/pub.dev/adjust_sdk-5.5.1/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"facebook_app_events","path":"/Users/sven/.pub-cache/hosted/pub.dev/facebook_app_events-0.26.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"in_app_purchase_android","path":"/Users/sven/.pub-cache/hosted/pub.dev/in_app_purchase_android-0.4.0+8/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"play_install_referrer","path":"/Users/sven/.pub-cache/hosted/pub.dev/play_install_referrer-0.5.0/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.21/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"in_app_purchase_storekit","path":"/Users/sven/.pub-cache/hosted/pub.dev/in_app_purchase_storekit-0.4.8+1/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"path_provider_linux","path":"/Users/sven/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"path_provider_windows","path":"/Users/sven/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"shared_preferences_web","path":"/Users/sven/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"adjust_sdk","dependencies":[]},{"name":"facebook_app_events","dependencies":[]},{"name":"in_app_purchase","dependencies":["in_app_purchase_android","in_app_purchase_storekit"]},{"name":"in_app_purchase_android","dependencies":[]},{"name":"in_app_purchase_storekit","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"play_install_referrer","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2026-03-25 14:45:33.520021","version":"3.41.4","swift_package_manager_enabled":{"ios":false,"macos":false}}

707
docs/README.md Normal file
View File

@ -0,0 +1,707 @@
# Client Proxy Framework 使用指南
## 1. 框架概述
`client_proxy_framework` 是一个通用代理 API 框架,用于 Flutter 移动应用与后端 API 的通信。框架已封装好请求/响应的加密解密、字段映射等逻辑,换皮应用只需配置少量参数即可使用。
### 核心特性
- **自动加解密**:请求体和响应体自动进行 AES 加密/解密
- **字段映射**:框架自动将原始字段名转换为 V2 字段名,响应自动转换回来
- **强类型实体**:返回强类型实体类,开发者直接使用映射后的字段
- **统一响应**`EntityResponse<T>` 提供强类型返回值
---
## 2. 快速开始
### 2.1 添加依赖
`pubspec.yaml` 中添加:
```yaml
dependencies:
client_proxy_framework:
path: ../client_proxy_framework
```
### 2.2 实现配置
创建一个类继承 `AppConfig`
```dart
import 'package:client_proxy_framework/client_proxy_framework.dart';
class MyAppConfig extends AppConfig {
@override
String get appId => 'YourAppId';
@override
String get packageName => 'com.yourapp.package';
@override
String get aesKey => 'your-16-char-key';
@override
String get preBaseUrl => 'https://pre-api.example.com';
@override
String get prodBaseUrl => 'https://api.example.com';
@override
String get proxyPath => '/quester/defender/summoner';
}
```
### 2.3 初始化
`main.dart` 中初始化:
```dart
void main() {
ApiClient.init(MyAppConfig());
runApp(const MyApp());
}
```
---
## 3. 设计理念
### 3.1 请求流程
```
调用层 (原始字段)
ProxyClient.request()
字段映射 (原始 → V2)
AES 加密
发送请求
```
### 3.2 响应流程
```
接收响应
AES 解密
字段映射 (V2 → 原始)
转换为实体类
调用层 (实体类)
```
---
## 4. 配置项详解
### AppConfig 配置
| 配置项 | 必填 | 说明 |
|--------|------|------|
| `appId` | 是 | 应用标识,对应代理请求的 `hero_class` |
| `packageName` | 是 | 应用包名,如 `com.example.app` |
| `aesKey` | 是 | AES 加密密钥,长度需为 16 字符 |
| `preBaseUrl` | 是 | 预发环境域名 |
| `prodBaseUrl` | 是 | 生产环境域名 |
| `proxyPath` | 是 | 代理入口路径 |
| `debugBaseUrlOverride` | 否 | 调试时本地代理地址 |
| `fieldMapping` | 否 | 字段映射表,默认使用 `petsHeroAIFieldMapping` |
---
## 5. API 服务
所有 API 方法使用**原始字段名**,返回**强类型实体**。
### 5.1 UserApi - 用户相关
#### 5.1.1 fastLogin - 设备快速登录
```dart
final res = await UserApi.fastLogin(
deviceId: '设备ID',
sign: 'MD5(deviceId)大写',
referer: '归因来源', // 可选
ch: '渠道号', // 可选
type: '类型', // 可选
);
if (res.isSuccess) {
final loginInfo = res.data!;
final token = loginInfo.userToken;
final credits = loginInfo.credits;
final userId = loginInfo.userId;
}
```
**返回实体**: `FastLoginResponse`
- `userToken`: 用户 Token
- `userId`: 用户 ID
- `credits`: 积分
- `avatar`: 头像
- `userName`: 用户名
- `countryCode`: 国家码
- `isVip`: 是否 VIP
- 等等...
#### 5.1.2 getCommonInfo - 获取用户通用信息
```dart
final res = await UserApi.getCommonInfo(
app: '应用ID',
userId: '用户ID', // 可选
);
if (res.isSuccess) {
final info = res.data!;
}
```
**返回实体**: `CommonInfoResponse`
#### 5.1.3 getAccount - 获取账户信息
```dart
final res = await UserApi.getAccount(
app: '应用ID',
userId: '用户ID', // 可选
);
if (res.isSuccess) {
final account = res.data!;
final credits = account.credits;
final isVip = account.isVip;
}
```
**返回实体**: `AccountResponse`
- `credits`: 积分
- `avatar`: 头像
- `userName`: 用户名
- `isVip`: 是否 VIP
- `freeTimes`: 免费次数
---
### 5.2 PaymentApi - 支付相关
#### 5.2.1 getGooglePayActivities - 获取 Android 商品列表
```dart
final res = await PaymentApi.getGooglePayActivities(
app: '应用ID', // 可选
country: '国家', // 可选
);
if (res.isSuccess) {
final products = res.data!.productList;
for (final product in products ?? []) {
final id = product.productId;
final price = product.actualAmount;
final bonus = product.bonus;
}
}
```
**返回实体**: `PaymentProductsResponse`
- `productList`: 商品列表
- `productId`: 商品 ID
- `activityId`: 活动 ID
- `actualAmount`: 实际金额
- `originAmount`: 原价
- `bonus`: 赠送积分
- `title`: 标题
#### 5.2.2 getPaymentMethods - 获取支付方式列表
```dart
final res = await PaymentApi.getPaymentMethods(
activityId: '活动ID',
country: '国家', // 可选
);
if (res.isSuccess) {
final methods = res.data!.paymentMethods;
for (final method in methods ?? []) {
final pm = method.paymentMethod;
final name = method.name;
final isRecommend = method.recommend;
}
}
```
**返回实体**: `PaymentMethodsResponse`
- `paymentMethods`: 支付方式列表
- `paymentMethod`: 支付方式
- `subPaymentMethod`: 子支付方式
- `name`: 显示名称
- `icon`: 图标
- `recommend`: 是否推荐
#### 5.2.3 createPayment - 创建支付订单
```dart
final res = await PaymentApi.createPayment(
app: '应用ID',
userId: '用户ID',
activityId: '活动ID',
paymentMethod: '支付方式',
paymentType: '支付子类型', // 可选
);
if (res.isSuccess) {
final order = res.data!;
final orderId = order.orderId;
final payUrl = order.payUrl;
}
```
**返回实体**: `CreatePaymentResponse`
- `orderId`: 订单 ID
- `payUrl`: 支付链接
- `status`: 状态
#### 5.2.4 getOrderDetail - 获取订单详情
```dart
final res = await PaymentApi.getOrderDetail(
userId: '用户ID',
orderId: '订单ID',
);
if (res.isSuccess) {
final order = res.data!;
final status = order.status;
}
```
**返回实体**: `OrderDetailResponse`
- `orderId`: 订单 ID
- `status`: 状态
- `amount`: 金额
#### 5.2.5 googlepay - Google Pay 回调
```dart
final res = await PaymentApi.googlepay(
signature: '购买签名',
purchaseData: '购买数据',
orderId: '订单ID',
userId: '用户ID',
);
if (res.isSuccess) {
final result = res.data!;
final status = result.status;
}
```
**返回实体**: `GooglePayCallbackResponse`
- `orderId`: 订单 ID
- `status`: 状态
- `creditsAdded`: 是否已加积分
---
### 5.3 ImageApi - 图片/视频相关
#### 5.3.1 getCategoryList - 获取分类列表
```dart
final res = await ImageApi.getCategoryList();
if (res.isSuccess) {
final categories = res.data!.categories;
}
```
**返回实体**: `CategoryListResponse`
- `categories`: 分类列表
- `id`: ID
- `name`: 名称
- `icon`: 图标
#### 5.3.2 getImg2VideoTasks - 获取任务列表
```dart
final res = await ImageApi.getImg2VideoTasks(
categoryId: 123, // 可选
);
if (res.isSuccess) {
final tasks = res.data!.tasks;
}
```
**返回实体**: `TasksResponse`
#### 5.3.3 getProgress - 查询任务进度
```dart
final res = await ImageApi.getProgress(
app: '应用ID',
taskId: '任务ID',
userId: '用户ID', // 可选
);
if (res.isSuccess) {
final progress = res.data!;
final status = progress.status;
final resultUrl = progress.resultUrl;
}
```
**返回实体**: `ProgressResponse`
- `taskId`: 任务 ID
- `status`: 状态
- `progress`: 进度 (0-100)
- `resultUrl`: 结果 URL
#### 5.3.4 createTask - 创建任务
```dart
final res = await ImageApi.createTask(
userId: '用户ID',
prompt: '提示词', // 可选
resolution: '1024x1024', // 可选
imgUrl: '图片URL', // 可选
allowance: false, // 可选
);
if (res.isSuccess) {
final taskId = res.data!.taskId;
}
```
**返回实体**: `CreateTaskResponse`
- `taskId`: 任务 ID
- `status`: 状态
#### 5.3.5 getMyTasks - 获取我的任务列表
```dart
final res = await ImageApi.getMyTasks(
app: '应用ID',
page: '1', // 可选
pageSize: '10', // 可选
cursor: '游标', // 可选
);
if (res.isSuccess) {
final tasks = res.data!.tasks;
final total = res.data!.total;
}
```
**返回实体**: `MyTasksResponse`
- `tasks`: 任务列表
- `total`: 总数
- `cursor`: 游标
#### 5.3.6 getCreditsPageInfo - 获取积分页面信息
```dart
final res = await ImageApi.getCreditsPageInfo(
app: '应用ID',
userId: '用户ID', // 可选
ch: '渠道', // 可选
);
if (res.isSuccess) {
final info = res.data!;
final credits = info.credits;
final freeTimes = info.freeTimes;
final isVip = info.isVip;
}
```
**返回实体**: `CreditsPageInfoResponse`
- `credits`: 积分
- `freeTimes`: 免费次数
- `isVip`: 是否 VIP
- `vipExpireTime`: VIP 过期时间
---
### 5.4 FeedbackApi - 举报/反馈相关
#### 5.4.1 getUploadPresignedUrl - 获取上传预签名 URL
```dart
final res = await FeedbackApi.getUploadPresignedUrl(
fileName: 'image.jpg',
);
if (res.isSuccess) {
final result = res.data!;
final uploadUrl = result.uploadUrl;
final filePath = result.filePath;
}
```
**返回实体**: `FeedbackUploadPresignedUrlResponse`
- `uploadUrl`: 上传 URL
- `filePath`: 文件路径
#### 5.4.2 submit - 提交反馈
```dart
final res = await FeedbackApi.submit(
fileUrls: ['https://...'],
content: '反馈内容',
contentType: 'text/plain',
);
if (res.isSuccess) {
final success = res.data!.success;
}
```
**返回实体**: `SubmitFeedbackResponse`
- `success`: 是否成功
- `feedbackId`: 反馈 ID
---
## 6. 响应处理
### 6.1 EntityResponse
所有返回实体类的方法都使用 `EntityResponse<T>`
```dart
final res = await UserApi.fastLogin(...);
// 检查成功
if (res.isSuccess) {
// 访问实体
final data = res.data!;
final token = data.userToken;
} else {
// 处理错误
print('Error: ${res.msg}');
}
```
### EntityResponse 属性
| 属性 | 类型 | 说明 |
|------|------|------|
| `code` | int | 响应码0 表示成功 |
| `msg` | String | 响应消息 |
| `data` | T? | 实体数据 |
| `isSuccess` | bool | 便捷属性code == 0 时为 true |
---
## 7. 设置用户 Token
登录成功后,调用以下方法设置用户 Token
```dart
// 登录成功后
final res = await UserApi.fastLogin(...);
if (res.isSuccess) {
ApiClient.instance.setUserToken(res.data!.userToken);
}
// 登出时
ApiClient.instance.setUserToken(null);
```
---
## 8. 调试模式
框架会自动根据 `kDebugMode` 选择环境:
- **调试模式**:使用 `preBaseUrl`(或 `debugBaseUrlOverride`
- **发布模式**:使用 `prodBaseUrl`
---
## 9. 字段映射
框架自动处理字段映射,调用层使用原始字段名。
### 请求字段映射(原始 → V2
| 原始字段 | V2 字段 |
|----------|---------|
| app | sentinel |
| userId | asset |
| deviceId | origin |
| sign | resolution |
| referer | digest |
| activityId | warrior |
| country | vambrace |
| paymentMethod | resource |
| paymentType | ceremony |
| orderId | federation |
| signature | sample |
| purchaseData | merchant |
| taskId | tree |
| prompt | ledger |
| resolution | guild |
| srcImgUrls | commission |
| fileName1 | gateway |
| contentType | pauldron |
| expectedSize | stronghold |
### 响应字段映射V2 → 原始)
| V2 字段 | 原始字段 |
|---------|----------|
| helm | code/响应码 |
| rampart | msg |
| sidekick | data |
| summon | productList |
| renew | paymentMethods |
| reveal | credits |
| reevaluate | userToken |
---
## 10. 代码示例
### 完整登录流程
```dart
import 'package:client_proxy_framework/client_proxy_framework.dart';
class AuthService {
static Future<bool> login() async {
final deviceId = await getDeviceId();
final sign = md5(deviceId).toUpperCase();
final res = await UserApi.fastLogin(
deviceId: deviceId,
sign: sign,
referer: 'organic',
);
if (res.isSuccess) {
final loginInfo = res.data!;
ApiClient.instance.setUserToken(loginInfo.userToken!);
UserState.setUserId(loginInfo.userId!);
UserState.setCredits(loginInfo.credits ?? 0);
return true;
}
return false;
}
}
```
### 完整支付流程
```dart
class PaymentService {
// 1. 获取商品列表
static Future<List<PaymentProductItem>> getProducts() async {
final res = await PaymentApi.getGooglePayActivities();
return res.data?.productList ?? [];
}
// 2. 获取支付方式
static Future<List<PaymentMethodItem>> getPaymentMethods(String activityId) async {
final res = await PaymentApi.getPaymentMethods(activityId: activityId);
return res.data?.paymentMethods ?? [];
}
// 3. 创建订单
static Future<String?> createOrder({
required String userId,
required String activityId,
required String paymentMethod,
}) async {
final res = await PaymentApi.createPayment(
app: ApiClient.instance.config.appId,
userId: userId,
activityId: activityId,
paymentMethod: paymentMethod,
);
if (res.isSuccess) {
return res.data?.orderId;
}
return null;
}
// 4. Google Pay 回调
static Future<bool> verifyGooglePay({
required String signature,
required String purchaseData,
required String orderId,
required String userId,
}) async {
final res = await PaymentApi.googlepay(
signature: signature,
purchaseData: purchaseData,
orderId: orderId,
userId: userId,
);
return res.isSuccess;
}
}
```
---
## 11. 常见问题
### Q: 如何修改字段映射?
A: 覆盖 `AppConfig.fieldMapping`
```dart
@override
FieldMapping get fieldMapping => const FieldMapping({
'deviceId': 'origin',
'userId': 'asset',
// ... 其他字段
});
```
### Q: 响应 data 为空怎么办?
A: 检查以下几点:
1. `ApiClient.init()` 是否已调用
2. 网络是否正常
3. `appId``aesKey` 等配置是否正确
---
## 12. 文件结构
```
lib/
├── client_proxy_framework.dart # 入口文件
└── src/
├── api/
│ ├── api_client.dart # 全局客户端
│ ├── api_crypto.dart # 加解密工具
│ ├── api_response.dart # 响应对象
│ └── proxy_client.dart # 代理请求客户端(含实体转换)
├── config/
│ ├── app_config.dart # 应用配置抽象类
│ ├── default_field_mapping.dart # 默认字段映射
│ └── field_mapping.dart # 字段映射类
├── entities/
│ ├── entity.dart # 实体基类
│ ├── user_entities.dart # 用户相关实体
│ ├── payment_entities.dart # 支付相关实体
│ ├── image_entities.dart # 图片/视频实体
│ └── feedback_entities.dart # 反馈实体
├── log/
│ └── app_logger.dart # 日志工具
└── services/
├── user_api.dart # 用户 API
├── payment_api.dart # 支付 API
├── image_api.dart # 图片/视频 API
└── feedback_api.dart # 反馈 API
```

View File

@ -0,0 +1,186 @@
# 新应用配置模板
创建新应用时,请填写以下配置信息。
***
## 一、应用基本信息
| 配置项 | 值 | 说明 |
| ------------- | ------------------ | ------------------- |
| 应用名称 | `填写应用名称` | 如FunyMee AI |
| Android 包名 | `填写 Android 包名` | 如com.funymeeai.app |
| iOS Bundle ID | `填写 iOS Bundle ID` | 如com.funymeeai.app |
| 应用 IDappId | `填写 appId` | 后台接口参数用 |
***
## 二、服务器配置
| 配置项 | 值 | 说明 |
| --------- | ------------- | ------------------------------- |
| 预发布环境 URL | `填写预发布环境 URL` | 如:<https://pre-api.example.com> |
| 生产环境 URL | `填写生产环境 URL` | 如:<https://api.example.com> |
| 代理接口路径 | `填写代理接口路径` | 如:/v1/proxy |
***
## 三、代理请求体字段配置
| 配置项 | 值 | 说明 |
| ------------------- | --------- | ----------------------------------------------------- |
| appId 明文 | `填写字段名` | 如hero\_class |
| 原始path | `填写字段名` | 如pet\_species |
| "POST"或"GET" | `填写字段名` | 如power\_level |
| 映射后的Header字段名构造JSON | `填写字段名` | 如quest\_rank |
| 映射后的URL参数字段名构造JSON | `填写字段名` | 如battle\_score |
| V2包装后的业务数据 | `填写字段名` | 如loyalty\_index |
| 噪音字段名列表 | `填写字段名列表` | 如:\['billing\_addr', 'utm\_term', 'cluster\_id', ...] |
***
## 四、V2 包装配置
| 配置项 | 值 | 说明 |
| ----------- | --------- | ---------------------------------------------------------- |
| V2 层级 | `填写字段名` | 如arsenal |
| V2 层级值 | `填写值` | 如4 |
| V2 层级路径 | `填写路径` | 如:\['vault', 'tome', 'codex', 'grimoire', 'sanctum'] |
| 噪音字段名列表 | `填写字段名列表` | 如:\['roar', 'clash', 'thunder', 'rumble', 'howl', 'growl'] |
***
## 五、安全配置
| 配置项 | 值 | 说明 |
| ------ | ---------------- | --------------- |
| AES 密钥 | `填写 16 字节 AES 密钥` | 用于请求加密AES-128 需要 16 字符 |
> **密钥长度说明**
> - AES-12816 字节16 个字符)
> - AES-25632 字节32 个字符)
>
> 当前框架使用 AES-128-ECB 模式,密钥固定为 16 字符。
***
## 六、Adjust SDK 配置
| 配置项 | 值 | 说明 |
| ----------- | ------------------------ | -------------------------- |
| App Token | `填写 Adjust App Token` | Adjust Dashboard 获取 |
| Environment | `sandbox` / `production` | 测试用 sandbox正式用 production |
### Adjust 事件 Token如有自定义
| 事件名称 | 事件 Token |
| --------- | ---------- |
| 首充 | `填写 token` |
| 购买 | `填写 token` |
| 注册 | `填写 token` |
| $5.99 档位 | `填写 token` |
| $9.99 档位 | `填写 token` |
| $19.99 档位 | `填写 token` |
| $49.99 档位 | `填写 token` |
| $99.99 档位 | `填写 token` |
***
## 七、Facebook SDK 配置
| 配置项 | 值 | 说明 |
| ------------ | -------------------- | ----------------------------- |
| App ID | `填写 Facebook App ID` | Facebook Developer Console 获取 |
| Client Token | `填写 Client Token` | Facebook Developer Console 获取 |
> MethodChannel 名称:框架自动使用 `{packageName}/facebook_sdk`,无需配置
***
## 八、字段映射配置(可选)
如有特殊字段映射需求请填写,默认使用框架默认映射。
| 原始字段名 | 映射后字段名 | 说明 |
| ------ | ------ | ------ |
| <br /> | <br /> | <br /> |
***
## 九、其他配置(如有)
| 配置项 | 值 | 说明 |
| ----------- | ----------- | ---------- |
| 调试基础 URL 覆盖 | `填写调试用 URL` | 仅调试时使用,可留空 |
| <br /> | <br /> | <br /> |
***
## 十、填写完成后的配置示例
```dart
// app_config.dart
class NewAppConfig extends AppConfig {
@override
String get appId => 'your_app_id';
@override
String get packageName => 'com.example.app';
@override
String get aesKey => 'your_16_char_key'; // AES-128 需要 16 字符
@override
String get preBaseUrl => 'https://pre-api.example.com';
@override
String get prodBaseUrl => 'https://api.example.com';
@override
String get proxyPath => '/v1/proxy';
@override
ProxyKeysConfig get proxyKeys => const ProxyKeysConfig(
appIdField: 'hero_class',
pathField: 'pet_species',
methodField: 'power_level',
headerField: 'quest_rank',
paramsField: 'battle_score',
bodyField: 'loyalty_index',
noiseKeys: ['billing_addr', 'utm_term', 'cluster_id', 'lsn_value', 'accuracy_val', 'dir_path'],
);
@override
String get v2LevelField => 'arsenal';
@override
int get v2LevelFixedValue => 4;
@override
List<String> get v2SanctumPath =>
const ['vault', 'tome', 'codex', 'grimoire', 'sanctum'];
@override
List<String> get v2NoiseKeys =>
const ['roar', 'clash', 'thunder', 'rumble', 'howl', 'growl'];
}
// main.dart
await AnalyticsService.init(
AnalyticsConfig(
packageName: 'com.example.app',
adjustConfig: AdjustConfig(
appToken: 'your_adjust_app_token',
environment: AdjustEnv.sandbox,
),
facebookConfig: FacebookConfig(
appId: 'your_facebook_app_id',
clientToken: 'your_facebook_client_token',
),
),
);
```
***
**填写完成后发送给我,我将根据此配置创建完整的应用代码。**

View File

@ -14,13 +14,13 @@
├─ enableThirdPartyPayment === true 且已登录 ├─ enableThirdPartyPayment === true 且已登录
│ │ │ │
│ ├─ getPaymentMethods(activityId) 获取支付方式 │ ├─ getPaymentMethods(activityId) 获取支付方式
│ ├─ 弹窗选择支付方式_PaymentMethodDialog │ ├─ 弹窗选择支付方式
│ ├─ createPayment 创建订单 │ ├─ createPayment 创建订单
│ │ │ │
│ ├─ 若选中的是 Google Payresource/ceremony == "GooglePay" │ ├─ 若选中的是 Google Pay
│ │ ├─ 调起 Google Play 内购 │ │ ├─ 调起 Google Play 内购
│ │ ├─ 拿到 serverVerificationData │ │ ├─ 拿到 purchaseData + signature
│ │ └─ POST /v1/payment/googlepay 回调验证 │ │ └─ googlepay 回调验证
│ │ │ │
│ └─ 否则(其他支付方式) │ └─ 否则(其他支付方式)
│ └─ 打开 payUrl 在外部浏览器完成支付 │ └─ 打开 payUrl 在外部浏览器完成支付
@ -46,55 +46,82 @@
### 4.1 接口 ### 4.1 接口
- **Android**: `GET /v1/payment/getGooglePayActivities` ```dart
- **iOS**: `GET /v1/payment/getApplePayActivities` // Android
final res = await PaymentApi.getGooglePayActivities(
app: '应用ID', // 可选
country: '国家', // 可选
);
### 4.2 商品字段映射 // iOS
final res = await PaymentApi.getApplePayActivities(
app: '应用ID', // 可选
country: '国家', // 可选
);
```
| 字段API | 字段(客户端映射) | 说明 | ### 4.2 返回实体
|-------------|-------------------|------|
| helm | code / productId | Google Play 商品 ID |
| warrior | activityId | 活动 ID用于创建订单 |
| guardian | actualAmount | 实际金额 |
| curriculum | originAmount | 原价(带划线)|
| forge | bonus | 赠送积分 |
| glossary | title | 标题 |
### 4.3 代码入口 ```dart
class PaymentProductsResponse {
List<PaymentProductItem>? productList;
}
文件:`lib/features/recharge/recharge_screen.dart` class PaymentProductItem {
- `_fetchActivities()`: 获取商品列表 String? productId; // 商品 ID (对应 helm)
- `_onBuy()`: 用户点击购买入口 String? activityId; // 活动 ID (对应 warrior)
String? actualAmount; // 实际金额 (对应 guardian)
String? originAmount; // 原价 (对应 curriculum)
int? bonus; // 赠送积分 (对应 forge)
String? title; // 标题 (对应 glossary)
}
```
--- ---
## 5. 第三方支付流程 ## 5. 第三方支付流程
### 5.1 步骤 ### 5.1 获取支付方式
1. **获取支付方式**: `POST /v1/payment/get-payment-methods` ```dart
- 参数: `warrior` (activityId), `vambrace` (可选,国家) final res = await PaymentApi.getPaymentMethods(
activityId: '活动ID',
country: '国家', // 可选
);
2. **弹窗选择**: 展示支付方式列表(`_PaymentMethodSheet`),包含: if (res.isSuccess) {
- `resource`: 支付方式(如 GOOGLEPAY final methods = res.data!.paymentMethods;
- `ceremony`: 子支付方式 // methods 包含:
- `name`: 显示名称 // - paymentMethod: 支付方式 (如 GOOGLEPAY)
- `icon`: 图标 URL // - subPaymentMethod: 子支付方式
- `recommend`: 是否推荐 // - name: 显示名称
// - icon: 图标 URL
// - recommend: 是否推荐
}
```
3. **创建订单**: `POST /v1/payment/createPayment` ### 5.2 创建订单
- 参数: `sentinel`, `asset`(userId), `warrior`(activityId), `resource`, `ceremony`
- 返回: `federation`(订单ID), `convert`(支付URL)
4. **支付方式分支**: ```dart
- **Google Pay**: 调用 `GooglePlayPurchaseService.launchPurchaseAndReturnData()` → 调起内购 → 调用 `PaymentApi.googlepay()` 回调验证 final res = await PaymentApi.createPayment(
- **其他方式**: 使用 `url_launcher` 打开 `convert` 支付链接 app: '应用ID',
userId: '用户ID',
activityId: '活动ID',
paymentMethod: '支付方式',
paymentType: '支付子类型', // 可选
);
### 5.2 代码位置 if (res.isSuccess) {
final order = res.data!;
final orderId = order.orderId; // 订单 ID
final payUrl = order.payUrl; // 支付链接
}
```
- 入口: `recharge_screen.dart``_runThirdPartyPayment()` ### 5.3 支付分支
- 创建订单: `_createOrderAndOpenUrl()`
- Google Pay 判断: `_isGooglePay()` - **Google Pay**: 调起内购 → 获取 purchaseData/signature → 调用 `googlepay` 回调
- **其他方式**: 打开 `payUrl` 在外部浏览器
--- ---
@ -102,44 +129,78 @@
仅 Android且不经过 `getPaymentMethods``createPayment`(三方支付关闭时): 仅 Android且不经过 `getPaymentMethods``createPayment`(三方支付关闭时):
1. 调用 `createPayment`resource=GooglePay, ceremony=GooglePay ```dart
2. 调起 Google Play 内购 // 1. 创建订单(直接走谷歌支付)
3. 回调验证 final createRes = await PaymentApi.createPayment(
app: '应用ID',
userId: '用户ID',
activityId: '活动ID',
paymentMethod: 'GooglePay',
paymentType: 'GooglePay',
);
### 代码位置 // 2. 调起 Google Play 内购
final purchaseResult = await GooglePlayPurchaseService.launchPurchaseAndReturnData(
productId: '商品ID',
);
- `recharge_screen.dart``_runGooglePay()` // 3. 回调验证
if (purchaseResult != null) {
final res = await PaymentApi.googlepay(
signature: purchaseResult.payload.signature,
purchaseData: purchaseResult.payload.purchaseData,
orderId: orderId ?? purchaseResult.orderId,
userId: userId,
);
}
```
--- ---
## 7. Google Play 内购统一入口 ## 7. Google Play 内购统一入口
### 7.1 核心方法 ### 7.1 调起内购
`GooglePlayPurchaseService.launchPurchaseAndReturnData(productId)` ```dart
final result = await GooglePlayPurchaseService.launchPurchaseAndReturnData(
productId: '商品ID',
);
- 调起 Google Play 内购 if (result != null) {
- 返回 `GooglePayPurchaseResult` 包含: // result.orderId: Google 订单号
- `orderId`: Google 订单号 // result.payload.purchaseData: 用于 merchant
- `payload.purchaseData`: purchaseData用于 merchant // result.payload.signature: 用于 signature
- `payload.signature`: 签名(用于 sample // result.purchaseDetails: PurchaseDetails 对象
- `purchaseDetails`: PurchaseDetails 对象 }
```
### 7.2 回调验证 ### 7.2 回调验证
`PaymentApi.googlepay(sample, merchant, federation, asset)` ```dart
final res = await PaymentApi.googlepay(
signature: result.payload.signature,
purchaseData: result.payload.purchaseData,
orderId: orderId,
userId: userId,
);
- `sample`: 签名 if (res.isSuccess) {
- `merchant`: purchaseData // 核销订单
- `federation`: 订单ID await GooglePlayPurchaseService.completeAndConsumePurchase(
- `asset`: userId result.purchaseDetails,
);
}
```
### 7.3 核销 ### 7.3 响应实体
`GooglePlayPurchaseService.completeAndConsumePurchase(purchaseDetails)` ```dart
class GooglePayCallbackResponse {
- 执行 `completePurchase` String? orderId;
- 执行 `consumePurchase`Android String? status; // SUCCESS / FAILED
bool? creditsAdded; // 是否已加积分
}
```
--- ---
@ -161,29 +222,47 @@
3. 补单成功后刷新账户 3. 补单成功后刷新账户
### 8.3 存储映射
使用 `SharedPreferences` 存储 `googleOrderId → federation` 映射:
- `saveFederationForGoogleOrderId()`
- `getFederationForGoogleOrderId()`
- `removeFederationForGoogleOrderId()`
--- ---
## 9. API 汇总 ## 9. API 汇总
| 接口 | 方法 | 说明 | | 接口 | 方法 | 返回实体 |
|------|------|------| |------|------|----------|
| `/v1/payment/getGooglePayActivities` | GET | 获取 Android 商品列表 | | `getGooglePayActivities` | GET | `PaymentProductsResponse` |
| `/v1/payment/getApplePayActivities` | GET | 获取 iOS 商品列表 | | `getApplePayActivities` | GET | `PaymentProductsResponse` |
| `/v1/payment/get-payment-methods` | POST | 获取支付方式列表 | | `getPaymentMethods` | POST | `PaymentMethodsResponse` |
| `/v1/payment/createPayment` | POST | 创建支付订单 | | `createPayment` | POST | `CreatePaymentResponse` |
| `/v1/payment/getOrderDetail` | GET | 查询订单状态(轮询)| | `getOrderDetail` | GET | `OrderDetailResponse` |
| `/v1/payment/googlepay` | POST | Google Pay 回调验证 | | `googlepay` | POST | `GooglePayCallbackResponse` |
--- ---
## 10. 代码文件位置 ## 10. 字段映射说明
框架自动完成字段映射,调用层使用原始字段名。
### 请求 → 响应 字段对照
| 业务含义 | 请求字段 | 响应字段 |
|----------|----------|----------|
| 应用 ID | app | - |
| 用户 ID | userId | - |
| 活动 ID | activityId | - |
| 支付方式 | paymentMethod | - |
| 支付子类型 | paymentType | - |
| 订单 ID | orderId | orderId |
| 购买签名 | signature | - |
| 购买数据 | purchaseData | - |
| 商品 ID | - | productId |
| 实际金额 | - | actualAmount |
| 原价 | - | originAmount |
| 赠送积分 | - | bonus |
| 支付链接 | - | payUrl |
| 订单状态 | - | status |
---
## 11. 代码文件位置
| 功能 | 文件路径 | | 功能 | 文件路径 |
|------|----------| |------|----------|
@ -197,14 +276,14 @@
--- ---
## 11. 常见问题 ## 12. 常见问题
### 11.1 商品未找到 ### 12.1 商品未找到
- 原因: 客户端 `helm` (productId) 与 Google Play 后台「产品 ID」不一致 - 原因: 客户端 `productId` 与 Google Play 后台「产品 ID」不一致
- 排查: 检查 `docs/google_pay_product_not_found.md` - 排查: 检查 Play 后台产品 ID 配置
### 11.2 补单 ### 12.2 补单
- 未确认订单可能不会出现在 `queryPastPurchases` - 未确认订单可能不会出现在 `queryPastPurchases`
- 应用启动时订阅 `purchaseStream` 接收重新下发 - 应用启动时订阅 `purchaseStream` 接收重新下发
@ -212,9 +291,9 @@
--- ---
## 12. 注意事项 ## 13. 注意事项
- 所有 Google Play 内购统一使用 `launchPurchaseAndReturnData()` 方法 - 所有 Google Play 内购统一使用 `launchPurchaseAndReturnData()` 方法
- 回调验证成功后必须调用 `completePurchase` + `consumePurchase` - 回调验证成功后必须调用 `completePurchase` + `consumePurchase`
- 支付 URL 打开方式取决于 `createPayment` 返回的 `convert` 字段 - 支付 URL 打开方式取决于 `createPayment` 返回的 `payUrl` 字段
- 订单状态轮询: 间隔 1/3/7/15/31/63 秒 - 订单状态轮询: 间隔 1/3/7/15/31/63 秒

View File

@ -0,0 +1,317 @@
# 第三方 SDK 集成配置指南
本文档说明换皮应用如何配置 Adjust、Facebook App Events 和 Google Play 内购三大 SDK。
---
## 配置文件
请填写以下配置文件(放在项目根目录或 `docs/` 目录下):
```json
{
"adjust": {
"app_token": "your_adjust_app_token",
"environment": "sandbox",
"event_tokens": {
"tier_599": "event_token_599",
"tier_999": "event_token_999",
"tier_1999": "event_token_1999",
"tier_4999": "event_token_4999",
"tier_9999": "event_token_9999",
"first_purchase": "event_token_first",
"purchase": "event_token_purchase",
"register": "event_token_register"
}
},
"facebook": {
"app_id": "your_facebook_app_id",
"client_token": "your_facebook_client_token"
},
"google_play": {
"product_ids": {
"tier_599": "com.example.product.599",
"tier_999": "com.example.product.999",
"tier_1999": "com.example.product.1999",
"tier_4999": "com.example.product.4999",
"tier_9999": "com.example.product.9999"
}
}
}
```
---
## 一、Adjust SDK 配置
### 1.1 所需信息
| 字段 | 说明 | 获取位置 |
|------|------|----------|
| `app_token` | Adjust App Token | Adjust Dashboard → App Settings |
| `environment` | `sandbox`(测试)或 `production`(正式) | 根据构建环境选择 |
### 1.2 Android 配置
> **重要**Facebook SDK 必须在 Flutter 引擎启动前初始化。以下步骤确保初始化顺序正确:
> 1. `Application.onCreate()` → 初始化 Facebook SDK
> 2. `MainActivity.configureFlutterEngine()` → 缓存 FlutterEngine
> 3. `Application` 通过 `FlutterEngineCache` 通知 Dart 层
**1. AndroidManifest.xml** - 添加权限和 Adjust App Token
```xml
<manifest>
<uses-permission android:name="android.permission.INTERNET"/>
<application>
<!-- Adjust 配置 -->
<meta-data
android:name="com.adjust.sdk.appToken"
android:value="your_adjust_app_token" />
<meta-data
android:name="com.adjust.sdk.environment"
android:value="sandbox" />
<!-- Facebook SDK 配置 -->
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" />
<meta-data
android:name="com.facebook.sdk.ClientToken"
android:value="@string/facebook_client_token" />
</application>
</manifest>
```
**2. res/values/strings.xml** - 添加 Facebook 配置:
```xml
<resources>
<string name="facebook_app_id">your_facebook_app_id</string>
<string name="facebook_client_token">your_facebook_client_token</string>
</resources>
```
**3. app/build.gradle** - 添加 Facebook 依赖:
```groovy
dependencies {
implementation 'com.facebook.android:facebook-core:18.0.0'
}
```
**4. 创建 Application 类** - Facebook SDK 初始化(**必须**
创建 `android/app/src/main/kotlin/<package>/App.kt`
```kotlin
package com.your.package.name
import android.app.Application
import android.os.Handler
import android.os.Looper
import com.facebook.FacebookSdk
import com.facebook.LoggingBehavior
import com.facebook.appevents.AppEventsLogger
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
class App : Application() {
companion object {
const val CHANNEL = "com.your.package.name/facebook_sdk"
const val ENGINE_ID = "main"
}
override fun onCreate() {
super.onCreate()
FacebookSdk.sdkInitialize(this)
FacebookSdk.setIsDebugEnabled(true)
FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS)
// 通知 Dart 层 Facebook SDK 已初始化
Handler(Looper.getMainLooper()).postDelayed({
val engine = FlutterEngineCache.getInstance().get(ENGINE_ID)
if (engine != null) {
MethodChannel(engine.dartExecutor.binaryMessenger, CHANNEL)
.invokeMethod("onFacebookSdkInitialized", null)
}
}, 100)
}
}
```
**5. 更新 MainActivity.kt** - 处理 SDK 初始化回调:
```kotlin
package com.your.package.name
import android.os.Handler
import android.os.Looper
import com.facebook.appevents.AppEventsLogger
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
companion object {
const val CHANNEL = "com.your.package.name/facebook_sdk"
const val ENGINE_ID = "main"
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
AppEventsLogger.activateApp(application)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, _ ->
if (call.method == "waitForFacebookSdkInit") {
AppEventsLogger.activateApp(application)
}
}
// 缓存 FlutterEngine 供 Application 类使用
Handler(Looper.getMainLooper()).postDelayed({
FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine)
}, 100)
}
}
```
**6. 更新 AndroidManifest.xml** - 使用 Application 类:
```xml
<application
android:name=".App"
android:label="Your App Name"
...>
```
**注意**Facebook SDK 必须在 Flutter 引擎启动前初始化,使用 Application 类 + MethodChannel 确保时序正确。
### 1.3 iOS 配置
**1. Info.plist** - 添加配置:
```xml
<key>AdjustAppToken</key>
<string>your_adjust_app_token</string>
<key>AdjustEnvironment</key>
<string>sandbox</string>
<key>FacebookAppID</key>
<string>your_facebook_app_id</string>
<key>FacebookClientToken</key>
<string>your_facebook_client_token</string>
<key>FacebookDisplayName</key>
<string>Your App Name</string>
```
---
## 二、Facebook App Events 配置
### 2.1 所需信息
| 字段 | 说明 | 获取位置 |
|------|------|----------|
| `app_id` | Facebook 应用 ID | Facebook Developer Console → Your App → Settings → Basic |
| `client_token` | 客户端口令 | Facebook Developer Console → Your App → Settings → Advanced |
### 2.2 配置步骤
**1.1****1.2** 中的 Android/iOS 配置说明添加 `facebook_app_id``facebook_client_token`
---
## 三、Google Play 内购配置
### 3.1 所需信息
| 字段 | 说明 | 获取位置 |
|------|------|----------|
| `product_ids` | Google Play Console 商品 ID | Google Play Console → Monetize → Products → Subscriptions/In-app products |
### 3.2 Android 配置
**1. AndroidManifest.xml** - 添加权限:
```xml
<uses-permission android:name="com.android.vending.BILLING"/>
```
**2. app/build.gradle** - 添加依赖:
```groovy
dependencies {
implementation 'com.android.billingclient:billing-ktx:6.0.0'
}
```
---
## 四、框架初始化代码
`main.dart` 中初始化:
```dart
import 'package:client_proxy_framework/client_proxy_framework.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. 初始化 API 客户端
ApiClient.init(YourAppConfig());
// 2. 初始化分析服务
AnalyticsService.init(
AnalyticsConfig(
adjustConfig: AdjustConfig(
appToken: 'your_adjust_app_token',
environment: AdjustEnv.sandbox, // 上线改为 AdjustEnv.production
logLevel: AdjustLogLevel.verbose,
),
facebookConfig: FacebookConfig(
appId: 'your_facebook_app_id',
debugLogs: true,
),
debugLogs: true,
),
);
// 3. 启动应用
runApp(const YourApp());
// 4. 初始化认证服务
await AuthService.init();
}
```
---
## 五、事件埋点示例
```dart
// Adjust 事件埋点
AnalyticsService.trackEvent('your_event_token');
// Facebook 购买事件
AnalyticsService.trackPurchase(amount: 9.99, currency: 'USD');
// Facebook 注册事件
AnalyticsService.trackRegister();
// Facebook 订阅事件
AnalyticsService.trackSubscribe('monthly_vip');
```
---
## 六、获取 SDK 配置信息的位置
### Adjust
- Dashboard: https://dashboard.adjust.com
- App Settings → App Tokens
### Facebook
- Developer Console: https://developers.facebook.com
- Settings → Basic (App ID)
- Settings → Advanced → Client Token
### Google Play
- Play Console: https://play.google.com/console
- Monetize → Products → Subscriptions/In-app products

View File

@ -0,0 +1,869 @@
# 换皮应用开发完整流程指南
本文档说明如何使用 `client_proxy_framework` 从零创建并完成一个换皮应用。
***
> **重要说明**:本指南**仅完成数据框架的对接**,包括:
>
> - API 请求封装与字段映射
> - 第三方 SDKAdjust、Facebook、Google Play集成
> - 用户认证与归因追踪
>
> **UI 组件需要另行对接**,包括:
>
> - 首页、生成页、个人中心等业务页面
> - 主题配色、字体、图标等 UI 样式
> - 业务组件和交互逻辑
>
> 详见本文档最后一章 [后续开发指引](#后续开发指引)。
***
## 目录
1. [准备阶段:创建项目目录](#1-准备阶段创建项目目录)
2. [填写配置模板](#2-填写配置模板)
3. [创建 Flutter 项目](#3-创建-flutter-项目)
4. [集成框架](#4-集成框架)
5. [配置应用信息](#5-配置应用信息)
6. [配置第三方-sdk](#6-配置第三方-sdk)
7. [实现业务代码](#7-实现业务代码)
8. [调试与测试](#8-调试与测试)
9. [上线准备](#9-上线准备)
10. [后续开发指引](#10-后续开发指引)
***
## 1. 准备阶段:创建项目目录
### 1.1 创建项目目录
在合适的位置创建项目目录:
```bash
# 请将 your_app_name 替换为实际的应用名称
mkdir your_app_name
cd your_app_name
```
### 1.2 复制配置模板
从框架目录复制配置模板到项目的 `docs` 目录下:
```bash
mkdir docs
cp ../client_proxy_framework/docs/new_app_config_template.md docs/
cp ../client_proxy_framework/docs/sdk_integration_guide.md docs/
```
复制后的目录结构:
```
your_app_name/
├── docs/
│ ├── new_app_config_template.md # 应用配置模板
│ └── sdk_integration_guide.md # SDK 集成指南
└── ...
```
### 1.3 打开配置模板
使用编辑器打开配置模板文件:
```bash
# 使用 VSCode
code docs/new_app_config_template.md
# 或使用其他编辑器
open docs/new_app_config_template.md
```
### 1.4 下一步
> **请先完成配置模板的填写**,再继续执行后续步骤。
>
> 配置模板包含以下信息:
> - 应用基本信息appId、packageName、AES 密钥等)
> - Adjust 配置App Token、Event Tokens
> - Facebook 配置App ID、Client Token
> - Google Play 配置(商品 ID
填写完成后,继续执行第 3 步。
***
## 2. 填写配置模板
请参考 `docs/new_app_config_template.md` 文件,填写以下配置信息:
### 2.1 应用基本信息
| 配置项 | 占位符 | 填写值 |
|-------|-------|-------|
| 应用名称 | `填写应用名称` | |
| Android 包名 | `填写 Android 包名` | |
| iOS Bundle ID | `填写 iOS Bundle ID` | |
| 应用 IDappId | `填写 appId` | |
### 2.2 服务器配置
| 配置项 | 占位符 | 填写值 |
|-------|-------|-------|
| 预发布环境 URL | `填写预发布环境 URL` | |
| 生产环境 URL | `填写生产环境 URL` | |
| 代理接口路径 | `填写代理接口路径` | |
### 2.3 Adjust SDK 配置
| 配置项 | 占位符 | 填写值 |
|-------|-------|-------|
| App Token | `填写 Adjust App Token` | |
| Environment | `sandbox` / `production` | |
### 2.4 Facebook SDK 配置
| 配置项 | 占位符 | 填写值 |
|-------|-------|-------|
| App ID | `填写 Facebook App ID` | |
| Client Token | `填写 Client Token` | |
### 2.5 AES 密钥
| 配置项 | 占位符 | 填写值 |
|-------|-------|-------|
| AES 密钥 | `填写 16 字符 AES-128 密钥` | |
> 框架使用 AES-128-ECB 模式,密钥固定为 16 字符。
### 2.6 下一步
> **配置填写完成后,继续执行第 3 步。**
>
> 如果配置复杂或有任何疑问,请将填写好的配置发送给我进行确认。
***
## 3. 创建 Flutter 项目
### 3.1 在项目目录下执行
确保你在之前创建的项目目录下执行:
```bash
# 如果不在项目目录下,先进入
cd your_app_name
```
### 3.2 创建 Flutter 项目
```bash
# 请将 com.yourcompany 替换为你的组织包名
flutter create --org com.yourcompany your_app_name
cd your_app_name
```
### 3.3 复制配置模板到项目
```bash
mkdir docs
cp ../client_proxy_framework/docs/new_app_config_template.md docs/
cp ../client_proxy_framework/docs/sdk_integration_guide.md docs/
```
### 3.4 下一步
> 继续执行 [第 4 步:集成框架](#4-集成框架)
***
## 4. 集成框架
### 4.1 添加依赖
编辑 `pubspec.yaml`
```yaml
dependencies:
flutter:
sdk: flutter
# 框架依赖
client_proxy_framework:
path: ../client_proxy_framework # 或使用 pub 发布的版本
# 框架需要的额外依赖
http: ^1.2.2
encrypt: ^5.0.3
crypto: ^3.0.3
logger: ^2.0.2
shared_preferences: ^2.2.2
device_info_plus: ^11.1.0
```
### 4.2 目录结构
建议按以下结构组织代码:
```
lib/
├── main.dart # 入口文件
├── app.dart # MaterialApp
├── core/
│ ├── config/
│ │ └── app_config.dart # 应用配置类
│ ├── auth/
│ │ └── auth_service.dart # 认证服务实现
│ ├── user/
│ │ └── user_state.dart # 用户状态管理
│ └── theme/
│ ├── app_colors.dart # 主题配色
│ ├── app_typography.dart # 字体样式
│ └── app_spacing.dart # 间距常量
├── features/
│ ├── home/ # 首页模块
│ ├── create/ # 生成页模块
│ ├── profile/ # 个人中心模块
│ └── recharge/ # 充值页模块
└── shared/
├── tab_selector_scope.dart # Tab 导航作用域
└── widgets/ # 共享组件
```
***
## 5. 配置应用信息
### 5.1 创建应用配置类
创建 `lib/core/config/app_config.dart`
```dart
import 'package:client_proxy_framework/client_proxy_framework.dart';
class YourAppConfig implements AppConfig {
@override
String get appId => 'your_app_id';
@override
String get packageName => 'com.yourcompany.yourapp';
@override
String get aesKey => 'your_16_char_key'; // AES-128 需要 16 字符
@override
String get preBaseUrl => 'https://pre-api.example.com';
@override
String get prodBaseUrl => 'https://api.example.com';
@override
String get proxyPath => '/v1/proxy';
@override
FieldMapping? get fieldMapping => DefaultFieldMapping.instance;
}
```
### 5.2 创建用户状态管理
创建 `lib/core/user/user_state.dart`
```dart
import 'package:flutter/foundation.dart';
class UserState {
static String? _userId;
static int _credits = 0;
static String? _avatar;
static String? _userName;
static String? _countryCode;
static String? get userId => _userId;
static int get credits => _credits;
static String? get avatar => _avatar;
static String? get userName => _userName;
static String? get countryCode => _countryCode;
static void setUserId(String id) => _userId = id;
static void setCredits(int credits) => _credits = credits;
static void setAvatar(String avatar) => _avatar = avatar;
static void setUserName(String name) => _userName = name;
static void setCountryCode(String code) => _countryCode = code;
}
```
***
## 6. 配置第三方 SDK
### 6.1 填写配置信息
参考 [SDK 集成配置指南](sdk_integration_guide.md),获取并填写以下信息:
| SDK | 必需信息 |
| ----------- | ---------------------- |
| Adjust | App Token, Environment |
| Facebook | App ID, Client Token |
| Google Play | 无(商品 ID 从后端获取) |
### 6.2 Android 配置
#### 6.2.1 创建 strings.xml
创建 `android/app/src/main/res/values/strings.xml`
```xml
<resources>
<string name="facebook_app_id">your_facebook_app_id</string>
<string name="facebook_client_token">your_facebook_client_token</string>
</resources>
```
#### 6.2.2 更新 AndroidManifest.xml
`<application>` 标签之前添加:
```xml
<!-- Adjust -->
<meta-data
android:name="com.adjust.sdk.appToken"
android:value="your_adjust_app_token" />
<meta-data
android:name="com.adjust.sdk.environment"
android:value="sandbox" />
<!-- Facebook -->
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" />
<meta-data
android:name="com.facebook.sdk.ClientToken"
android:value="@string/facebook_client_token" />
```
#### 6.2.3 创建 Application 类
创建 `android/app/src/main/kotlin/com/yourcompany/yourapp/App.kt`
```kotlin
package com.yourcompany.yourapp
import android.app.Application
import android.os.Handler
import android.os.Looper
import com.facebook.FacebookSdk
import com.facebook.LoggingBehavior
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
class App : Application() {
companion object {
const val CHANNEL = "com.yourcompany.yourapp/facebook_sdk"
const val ENGINE_ID = "main"
}
override fun onCreate() {
super.onCreate()
FacebookSdk.sdkInitialize(this)
FacebookSdk.setIsDebugEnabled(true)
FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS)
Handler(Looper.getMainLooper()).postDelayed({
val engine = FlutterEngineCache.getInstance().get(ENGINE_ID)
if (engine != null) {
MethodChannel(engine.dartExecutor.binaryMessenger, CHANNEL)
.invokeMethod("onFacebookSdkInitialized", null)
}
}, 100)
}
}
```
#### 6.2.4 创建 MainActivity
创建 `android/app/src/main/kotlin/com/yourcompany/yourapp/MainActivity.kt`
```kotlin
package com.yourcompany.yourapp
import android.os.Handler
import android.os.Looper
import com.facebook.appevents.AppEventsLogger
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
companion object {
const val CHANNEL = "com.yourcompany.yourapp/facebook_sdk"
const val ENGINE_ID = "main"
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
AppEventsLogger.activateApp(application)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"waitForFacebookSdkInit" -> {
AppEventsLogger.activateApp(application)
result.success(true) // 必须调用 result.success() 否则 Dart 端会卡住
}
"onFacebookSdkInitialized" -> {
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
Handler(Looper.getMainLooper()).postDelayed({
FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine)
}, 100)
}
}
```
> **注意**`result.success()` 必须被调用,否则 Dart 端的 `await` 会一直等待响应,导致应用卡住。
#### 6.2.5 更新 AndroidManifest.xml 使用 Application
```xml
<application
android:name=".App"
android:label="Your App Name"
android:icon="@mipmap/ic_launcher">
```
### 6.3 iOS 配置
#### 6.3.1 更新 Info.plist
`ios/Runner/Info.plist` 中添加:
```xml
<key>AdjustAppToken</key>
<string>your_adjust_app_token</string>
<key>AdjustEnvironment</key>
<string>sandbox</string>
<key>FacebookAppID</key>
<string>your_facebook_app_id</string>
<key>FacebookClientToken</key>
<string>your_facebook_client_token</string>
<key>FacebookDisplayName</key>
<string>Your App Name</string>
```
***
## 7. 实现业务代码
### 7.1 创建认证服务实现
创建 `lib/core/auth/auth_service.dart`
```dart
import 'dart:convert';
import 'package:client_proxy_framework/client_proxy_framework.dart';
import 'package:crypto/crypto.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import '../user/user_state.dart';
/// 归因回调实现
class AppAttributionCallbacks implements AttributionCallbacks {
@override
Future<String?> getReferrer() async {
final attribution = AnalyticsService.getAttribution();
if (attribution != null) {
return _attributionToJson(attribution);
}
return '';
}
String _attributionToJson(AttributionData data) {
final map = <String, dynamic>{
'trackerToken': data.trackerToken,
'trackerName': data.trackerName,
'network': data.network,
'campaign': data.campaign,
'adgroup': data.adgroup,
'creative': data.creative,
'clickLabel': data.clickLabel,
'costType': data.costType,
'costAmount': _jsonEncodableCostAmount(data.costAmount),
'costCurrency': data.costCurrency,
'jsonResponse': data.jsonResponse,
'fbInstallReferrer': data.fbInstallReferrer,
};
return base64Encode(utf8.encode(jsonEncode(map)));
}
Object? _jsonEncodableCostAmount(double? v) {
if (v == null) return null;
if (v.isNaN || !v.isFinite) return v.toString();
return v;
}
@override
Future<String?> getAdjustReferrer() async => getReferrer();
@override
Future<String?> getPlatformReferrer() async => null;
}
/// 认证回调实现
class AppAuthCallbacks implements AuthServiceCallbacks {
@override
Future<String> getDeviceId() async {
final deviceInfo = DeviceInfoPlugin();
switch (defaultTargetPlatform) {
case TargetPlatform.android:
final android = await deviceInfo.androidInfo;
return android.id;
case TargetPlatform.iOS:
final ios = await deviceInfo.iosInfo;
return ios.identifierForVendor ?? 'ios-unknown';
default:
return 'device-${DateTime.now().millisecondsSinceEpoch}';
}
}
@override
String computeSign(String deviceId) {
return md5.convert(utf8.encode(deviceId)).toString().toUpperCase();
}
@override
void onLoginSuccess(FastLoginResponse data) {
if (data.userId != null) UserState.setUserId(data.userId!);
if (data.credits != null) UserState.setCredits(data.credits!);
if (data.avatar != null) UserState.setAvatar(data.avatar!);
if (data.userName != null) UserState.setUserName(data.userName!);
}
@override
void onCommonInfoLoaded(CommonInfoResponse data) {
if (data.credits != null) UserState.setCredits(data.credits!);
if (data.avatar != null) UserState.setAvatar(data.avatar!);
if (data.userName != null) UserState.setUserName(data.userName!);
}
@override
void onLoginFailed(String msg) {
debugPrint('[AuthService] Login failed: $msg');
}
}
/// 认证服务
class AuthService {
static final _authCallbacks = AppAuthCallbacks();
static final _attributionCallbacks = AppAttributionCallbacks();
static Future<void> init() async {
AttributionService.init(_attributionCallbacks);
FrameworkAuthService.init(_authCallbacks);
await FrameworkAuthService.start();
}
static Future<void> get loginComplete => FrameworkAuthService.loginComplete;
}
```
### 7.2 创建 main.dart
创建 `lib/main.dart`
```dart
import 'package:flutter/material.dart';
import 'package:client_proxy_framework/client_proxy_framework.dart';
import 'app.dart';
import 'core/auth/auth_service.dart';
import 'core/config/app_config.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. 初始化 API 客户端
ApiClient.init(YourAppConfig());
// 2. 初始化分析服务
await AnalyticsService.init(
AnalyticsConfig(
packageName: 'com.yourcompany.yourapp', // 包名MethodChannel 使用
adjustConfig: AdjustConfig(
appToken: 'your_adjust_app_token',
environment: AdjustEnv.sandbox, // 上线改为 production
),
facebookConfig: FacebookConfig(
appId: 'your_facebook_app_id',
clientToken: 'your_facebook_client_token',
),
),
);
// 3. 提前获取归因
await AnalyticsService.initAttribution();
// 4. 启动应用
runApp(const App());
// 5. 初始化认证
AuthService.init();
}
```
### 7.3 创建 App 组件
创建 `lib/app.dart`
```dart
import 'package:flutter/material.dart';
import 'home_screen.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Your App Name',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomeScreen(),
);
}
}
```
***
## 8. 调试与测试
### 8.1 运行调试版本
```bash
flutter run
```
### 8.2 检查日志
运行时应看到以下日志:
```
D/Adjust (xxxxx): Adjust SDK started
I/flutter (xxxxx): [AuthService] start: deviceId=xxx
I/flutter (xxxxx): Facebook App Events initialized (from native callback)
```
### 8.3 常见问题排查
| 问题 | 解决方案 |
| --------------------------------- | ---------------------------------------- |
| Adjust 日志显示 "SANDBOX" | 正常,测试环境会显示 |
| Facebook 报错 "must be initialized" | 检查 AndroidManifest.xml 和 Application 类配置 |
| 归因未上报 | 检查 initAttribution() 是否在 main() 中调用 |
***
## 9. 上线准备
### 9.1 修改环境配置
#### main.dart
```dart
// Adjust 改为 production
adjustConfig: AdjustConfig(
appToken: 'your_production_app_token',
environment: AdjustEnv.production, // 修改这里
),
// Facebook 关闭调试日志
facebookConfig: FacebookConfig(
appId: 'your_facebook_app_id',
clientToken: 'your_facebook_client_token',
debugLogs: false, // 关闭调试日志
),
```
#### Android
```xml
<!-- AndroidManifest.xml -->
<meta-data
android:name="com.adjust.sdk.environment"
android:value="production" />
```
#### iOS
```xml
<!-- Info.plist -->
<key>AdjustEnvironment</key>
<string>production</string>
```
### 9.2 构建发布版本
```bash
# Android
flutter build apk --release
flutter build appbundle --release
# iOS
flutter build ios --release
```
### 9.3 检查清单
- [ ] Adjust App Token 已替换为生产环境
- [ ] Adjust Environment 已改为 production
- [ ] Facebook 调试日志已关闭
- [ ] Facebook Client Token 已更新(生产环境)
- [ ] 应用名称和包名正确
- [ ] 所有第三方 SDK 配置完成
***
## 附录:框架 API 快速参考
### 分析服务
```dart
// 初始化
await AnalyticsService.init(config);
// 埋点
AnalyticsService.trackEvent('event_token');
AnalyticsService.trackPurchase(amount: 9.99, currency: 'USD');
AnalyticsService.trackRegister();
AnalyticsService.trackSubscribe('monthly_vip');
// 获取归因
final attribution = AnalyticsService.getAttribution();
```
### 认证服务
```dart
// 获取登录完成状态
await AuthService.loginComplete;
// 发起登录
await AuthService.init();
```
### API 请求
```dart
// 获取代理客户端
final proxy = ApiClient.instance.proxy;
// GET 请求
final res = await proxy.request(
path: '/v1/endpoint',
method: 'GET',
);
// POST 请求
final res = await proxy.request(
path: '/v1/endpoint',
method: 'POST',
body: {'key': 'value'},
);
// 请求带字段映射的实体
final res = await proxy.requestEntity(
path: '/v1/entity',
method: 'GET',
entityFactory: YourEntity.fromJson,
);
```
***
## 10. 后续开发指引
完成本指南后,数据框架已就绪。接下来需要开发 **UI 业务层**
### 10.1 框架提供的 API 服务
| 服务类 | 用途 |
| ------------- | ------------- |
| `ImageApi` | 图片/视频生成相关 API |
| `PaymentApi` | 支付相关 API |
| `FeedbackApi` | 举报/反馈相关 API |
| `UserApi` | 用户账户相关 API |
### 10.2 框架提供的实体类
参考 `client_proxy_framework/lib/src/entities/` 目录:
| 实体类 | 用途 |
| ------------------ | ------------ |
| `UserEntities` | 用户、登录、通用信息响应 |
| `ImageEntities` | 分类、任务、生成结果 |
| `PaymentEntities` | 商品、订单、支付结果 |
| `FeedbackEntities` | 举报反馈相关 |
### 10.3 典型的 UI 开发流程
```dart
// 1. 在业务页面中使用框架 API
class HomeScreen extends StatefulWidget {
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
// 等待登录完成(如果需要)
await AuthService.loginComplete;
// 调用框架 API 获取数据
final res = await ImageApi.getCategoryList();
if (res.isSuccess) {
// 处理数据...
}
}
}
// 2. 使用 UserState 管理状态
UserState.setCredits(credits);
UserState.setUserId(userId);
// 3. 调用分析服务埋点
AnalyticsService.trackPurchase(amount: 9.99, currency: 'USD');
AnalyticsService.trackEvent('your_event_token');
```
### 10.4 建议的 UI 开发顺序
1. **首页HomeScreen** - 展示分类和任务卡片
2. **生成页CreateScreen** - 创建生成任务
3. **个人中心ProfileScreen** - 用户信息和设置
4. **充值页RechargeScreen** - 内购支付流程
### 10.5 参考实现
参考 `app_client_1` 项目中的 UI 实现:
- `lib/features/home/home_screen.dart` - 首页实现
- `lib/shared/widgets/` - 共享组件(按钮、卡片等)
- `lib/core/theme/` - 主题配置

View File

@ -11,9 +11,17 @@ export 'src/api/api_crypto.dart';
export 'src/api/api_response.dart'; export 'src/api/api_response.dart';
export 'src/api/proxy_client.dart'; export 'src/api/proxy_client.dart';
export 'src/config/app_config.dart'; export 'src/config/app_config.dart';
export 'src/config/attribution_config.dart';
export 'src/config/field_mapping.dart'; export 'src/config/field_mapping.dart';
export 'src/config/default_field_mapping.dart'; export 'src/config/default_field_mapping.dart';
export 'src/entities/entities.dart';
export 'src/log/app_logger.dart'; export 'src/log/app_logger.dart';
export 'src/services/adjust_service.dart';
export 'src/services/analytics_service.dart';
export 'src/services/auth_service.dart';
export 'src/services/facebook_service.dart';
export 'src/services/feedback_api.dart';
export 'src/services/image_api.dart'; export 'src/services/image_api.dart';
export 'src/services/payment_api.dart'; export 'src/services/payment_api.dart';
export 'src/services/payment_service.dart';
export 'src/services/user_api.dart'; export 'src/services/user_api.dart';

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../config/app_config.dart'; import '../config/app_config.dart';
import '../entities/entity.dart';
import '../log/app_logger.dart'; import '../log/app_logger.dart';
import 'api_crypto.dart'; import 'api_crypto.dart';
import 'api_response.dart'; import 'api_response.dart';
@ -51,7 +52,6 @@ void _log(Object? msg) {
_logLong(str); _logLong(str);
} }
/// json ['vault','tome','codex','grimoire','sanctum']
dynamic _getByPath(Map<String, dynamic> json, List<String> path) { dynamic _getByPath(Map<String, dynamic> json, List<String> path) {
dynamic current = json; dynamic current = json;
for (final key in path) { for (final key in path) {
@ -61,26 +61,27 @@ dynamic _getByPath(Map<String, dynamic> json, List<String> path) {
return current; return current;
} }
///
typedef EntityFactory<T extends Entity> = T Function(Map<String, dynamic>);
/// ///
class ProxyClient { class ProxyClient {
ProxyClient({ ProxyClient({
required this.config, required this.config,
this.baseUrlOverride, this.baseUrlOverride,
this.userToken, this.userToken,
}) : _crypto = ApiCrypto(aesKey: config.aesKey); }) : _crypto = ApiCrypto(aesKey: config.aesKey);
final AppConfig config; final AppConfig config;
final String? baseUrlOverride; final String? baseUrlOverride;
final ApiCrypto _crypto; final ApiCrypto _crypto;
/// Token [ApiClient.setUserToken]
String? userToken; String? userToken;
String get _baseUrl => baseUrlOverride ?? config.baseUrl; String get _baseUrl => baseUrlOverride ?? config.baseUrl;
Map<String, dynamic> _buildV2Wrapper(Map<String, dynamic> sanctum) { Map<String, dynamic> _buildV2Wrapper(Map<String, dynamic> sanctum) {
final result = Map<String, dynamic>.from(config.v2FixedValues); final result = Map<String, dynamic>.from(config.v2FixedValues);
// vault.tome.codex.grimoire.sanctum
Map<String, dynamic> current = result; Map<String, dynamic> current = result;
final path = config.v2SanctumPath; final path = config.v2SanctumPath;
for (var i = 0; i < path.length - 1; i++) { for (var i = 0; i < path.length - 1; i++) {
@ -96,9 +97,9 @@ class ProxyClient {
return result; return result;
} }
/// /// Map
/// ///
/// [headers][queryParams][body] 使****canonical /// [headers][queryParams][body] 使****
/// [AppConfig.fieldMapping] V2 /// [AppConfig.fieldMapping] V2
/// data V2 /// data V2
Future<ApiResponse> request({ Future<ApiResponse> request({
@ -113,10 +114,10 @@ class ProxyClient {
var headersMap = Map<String, dynamic>.from(headers ?? {}); var headersMap = Map<String, dynamic>.from(headers ?? {});
if (config.packageName.isNotEmpty) { if (config.packageName.isNotEmpty) {
headersMap['pkg'] = config.packageName; headersMap[mapping.headerPackageNameField] = config.packageName;
} }
if (userToken != null && userToken!.isNotEmpty) { if (userToken != null && userToken!.isNotEmpty) {
headersMap['User_token'] = userToken!; headersMap[mapping.headerUserTokenField] = userToken!;
} }
headersMap = mapping.mapRequest(headersMap); headersMap = mapping.mapRequest(headersMap);
@ -135,15 +136,15 @@ class ProxyClient {
final logStr = final logStr =
'========== 原始入参 ===========\npath: $path\nmethod: $method\nqueryParams: $paramsEncoded\nbody(sanctum): ${jsonEncode(sanctum)}'; '========== 原始入参 ===========\npath: $path\nmethod: $method\nqueryParams: $paramsEncoded\nbody(sanctum): ${jsonEncode(sanctum)}';
_log(logStr); logWithEmbeddedJson(logStr);
final proxyBody = <String, dynamic>{ final proxyBody = <String, dynamic>{
pk.heroClass: config.appId, pk.appIdField: config.appName,
pk.petSpecies: _crypto.encrypt(path), pk.pathField: _crypto.encrypt(path),
pk.powerLevel: _crypto.encrypt(method), pk.methodField: _crypto.encrypt(method),
pk.questRank: _crypto.encrypt(headersEncoded), pk.headerField: _crypto.encrypt(headersEncoded),
pk.battleScore: _crypto.encrypt(paramsEncoded), pk.paramsField: _crypto.encrypt(paramsEncoded),
pk.loyaltyIndex: _crypto.encrypt(v2BodyEncoded), pk.bodyField: _crypto.encrypt(v2BodyEncoded),
}; };
for (final key in pk.noiseKeys) { for (final key in pk.noiseKeys) {
@ -152,6 +153,7 @@ class ProxyClient {
final url = '$_baseUrl${config.proxyPath}'; final url = '$_baseUrl${config.proxyPath}';
logWithEmbeddedJson('========== 实际请求体 ===========\n${jsonEncode(proxyBody)}');
final response = await http.post( final response = await http.post(
Uri.parse(url), Uri.parse(url),
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
@ -161,25 +163,66 @@ class ProxyClient {
return _parseResponse(response); return _parseResponse(response);
} }
///
///
/// [headers][queryParams][body] 使****
/// [entityFactory] data
Future<EntityResponse<T>> requestEntity<T extends Entity>({
required String path,
required String method,
required EntityFactory<T> entityFactory,
Map<String, String>? headers,
Map<String, String>? queryParams,
Map<String, dynamic>? body,
}) async {
final response = await request(
path: path,
method: method,
headers: headers,
queryParams: queryParams,
body: body,
);
if (response.isSuccess && response.data is Map<String, dynamic>) {
final entity = entityFactory(response.data as Map<String, dynamic>);
return EntityResponse<T>(
code: response.code,
msg: response.msg,
data: entity,
);
}
return EntityResponse<T>(
code: response.code,
msg: response.msg,
data: null,
);
}
ApiResponse _parseResponse(http.Response response) { ApiResponse _parseResponse(http.Response response) {
try { try {
final decrypted = _crypto.decrypt(response.body); final decrypted = _crypto.decrypt(response.body);
final json = jsonDecode(decrypted) as Map<String, dynamic>; final json = jsonDecode(decrypted) as Map<String, dynamic>;
_log('========== 响应 ===========\n${jsonEncode(json)}'); logWithEmbeddedJson('========== 响应 ===========\n${jsonEncode(json)}');
final mapping = config.fieldMapping;
final sanctum = _getByPath(json, config.v2SanctumPath); final sanctum = _getByPath(json, config.v2SanctumPath);
final codeField = mapping.responseCodeField;
final msgField = mapping.responseMsgField;
final dataField = mapping.responseDataField;
final code = sanctum is Map<String, dynamic> final code = sanctum is Map<String, dynamic>
? (sanctum[config.responseCodeField] as int? ?? -1) ? (sanctum[codeField] as int? ?? -1)
: (json[config.responseCodeField] as int? ?? -1); : (json[codeField] as int? ?? -1);
final msg = sanctum is Map<String, dynamic> final msg = sanctum is Map<String, dynamic>
? (sanctum[config.responseMsgField] as String? ?? '') ? (sanctum[msgField] as String? ?? '')
: (json[config.responseMsgField] as String? ?? ''); : (json[msgField] as String? ?? '');
var data = sanctum is Map<String, dynamic> var data = sanctum is Map<String, dynamic>
? sanctum[config.responseDataField] ? sanctum[dataField]
: json[config.responseDataField]; : json[dataField];
if (data is Map<String, dynamic>) { if (data is Map<String, dynamic>) {
data = config.fieldMapping.mapResponse(data); data = mapping.mapResponse(data);
} }
return ApiResponse(code: code, msg: msg, data: data); return ApiResponse(code: code, msg: msg, data: data);
@ -188,3 +231,18 @@ class ProxyClient {
} }
} }
} }
///
class EntityResponse<T extends Entity> {
EntityResponse({
required this.code,
this.msg = '',
this.data,
});
final int code;
final String msg;
final T? data;
bool get isSuccess => code == 0;
}

View File

@ -6,28 +6,54 @@ import 'field_mapping.dart';
/// ///
class ProxyKeysConfig { class ProxyKeysConfig {
const ProxyKeysConfig({ const ProxyKeysConfig({
this.heroClass = 'hero_class', /// appId
this.petSpecies = 'pet_species', this.appIdField = 'hero_class',
this.powerLevel = 'power_level',
this.questRank = 'quest_rank', /// path
this.battleScore = 'battle_score', this.pathField = 'pet_species',
this.loyaltyIndex = 'loyalty_index',
/// "POST" "GET"
this.methodField = 'power_level',
/// Header JSON
this.headerField = 'quest_rank',
/// URL JSON
this.paramsField = 'battle_score',
/// V2
this.bodyField = 'loyalty_index',
///
this.noiseKeys = const [ this.noiseKeys = const [
'billing_addr', 'billing_addr',
'utm_term', 'utm_term',
'cluster_id', 'cluster_id',
'lsn_value', 'lsn_value',
'accuracy_val', 'accuracy_val',
'dir_path', 'dir_path'
], ],
}); });
final String heroClass; /// appId
final String petSpecies; final String appIdField;
final String powerLevel;
final String questRank; /// path
final String battleScore; final String pathField;
final String loyaltyIndex;
/// "POST" "GET"
final String methodField;
/// Header JSON
final String headerField;
/// URL JSON
final String paramsField;
/// V2
final String bodyField;
///
final List<String> noiseKeys; final List<String> noiseKeys;
} }
@ -36,6 +62,9 @@ class ProxyKeysConfig {
abstract class AppConfig { abstract class AppConfig {
AppConfig(); AppConfig();
///
String get appName;
/// hero_class /// hero_class
String get appId; String get appId;
@ -69,7 +98,7 @@ abstract class AppConfig {
/// ///
ProxyKeysConfig get proxyKeys => const ProxyKeysConfig(); ProxyKeysConfig get proxyKeys => const ProxyKeysConfig();
/// V2 sanctum ['vault','tome','codex','grimoire','sanctum'] /// V2
List<String> get v2SanctumPath => List<String> get v2SanctumPath =>
const ['vault', 'tome', 'codex', 'grimoire', 'sanctum']; const ['vault', 'tome', 'codex', 'grimoire', 'sanctum'];
@ -77,23 +106,16 @@ abstract class AppConfig {
List<String> get v2NoiseKeys => List<String> get v2NoiseKeys =>
const ['roar', 'clash', 'thunder', 'rumble', 'howl', 'growl']; const ['roar', 'clash', 'thunder', 'rumble', 'howl', 'growl'];
/// V2 arsenal: 4 /// V2
Map<String, dynamic> get v2FixedValues => const {'arsenal': 4}; String get v2LevelField => 'arsenal';
/// code /// V2
String get responseCodeField => 'helm'; int get v2LevelFixedValue => 4;
/// msg /// V2
String get responseMsgField => 'rampart'; Map<String, dynamic> get v2FixedValues {
return {v2LevelField: v2LevelFixedValue};
/// data }
String get responseDataField => 'sidekick';
///
String get headerPackageNameField => 'portal';
/// token
String get headerUserTokenField => 'knight';
/// V2 /// V2
/// V2 /// V2

View File

@ -0,0 +1,90 @@
import 'dart:async';
/// Adjust SDK
enum AdjustEnv {
sandbox,
production,
}
/// Adjust SDK
enum AdjustLogLevel {
off,
verbose,
}
/// Adjust SDK
class AdjustConfig {
const AdjustConfig({
required this.appToken,
this.environment = AdjustEnv.production,
this.logLevel = AdjustLogLevel.off,
this.fbAppId,
});
final String appToken;
final AdjustEnv environment;
final AdjustLogLevel logLevel;
final String? fbAppId;
}
/// Facebook SDK
class FacebookConfig {
const FacebookConfig({
required this.appId,
this.clientToken,
this.debugLogs = false,
});
final String appId;
final String? clientToken;
final bool debugLogs;
}
///
class PlatformAttributionConfig {
const PlatformAttributionConfig({
this.enabled = true,
});
final bool enabled;
}
///
abstract class AttributionCallbacks {
Future<String?> getReferrer();
Future<String?> getAdjustReferrer();
Future<String?> getPlatformReferrer();
}
///
class DefaultAttributionCallbacks implements AttributionCallbacks {
@override
Future<String?> getReferrer() async => '';
@override
Future<String?> getAdjustReferrer() async => null;
@override
Future<String?> getPlatformReferrer() async => null;
}
///
abstract class AttributionService {
static AttributionCallbacks? _callbacks;
static void init(AttributionCallbacks callbacks) {
_callbacks = callbacks;
}
static AttributionCallbacks get callbacks {
if (_callbacks == null) {
throw StateError('AttributionService not initialized');
}
return _callbacks!;
}
static Future<String?> getReferrer() => callbacks.getReferrer();
static Future<String?> getAdjustReferrer() => callbacks.getAdjustReferrer();
static Future<String?> getPlatformReferrer() =>
callbacks.getPlatformReferrer();
}

View File

@ -9,6 +9,11 @@ const FieldMapping petsHeroAIFieldMapping = FieldMapping({
'pkg': 'portal', 'pkg': 'portal',
'User_token': 'knight', 'User_token': 'knight',
// === ===
'code': 'helm',
'msg': 'rampart',
'data': 'sidekick',
// === query === // === query ===
'app': 'sentinel', 'app': 'sentinel',
'userId': 'asset', 'userId': 'asset',
@ -87,4 +92,9 @@ const FieldMapping petsHeroAIFieldMapping = FieldMapping({
'img': 'revenue', 'img': 'revenue',
'url': 'digitize', 'url': 'digitize',
'launchImgUrl': 'launchImgUrl', 'launchImgUrl': 'launchImgUrl',
// === ===
'fileName': 'layer',
'fileUrls': 'inventory',
'content': 'cloak',
}); });

View File

@ -16,12 +16,18 @@ class FieldMapping {
); );
} }
///
String mapField(String original) => mapping[original] ?? original;
/// V2
String reverseMapField(String v2) => _inverse[v2] ?? v2;
/// Map key /// Map key
Map<String, dynamic> mapRequest(Map<String, dynamic> input) { Map<String, dynamic> mapRequest(Map<String, dynamic> input) {
if (input.isEmpty) return input; if (input.isEmpty) return input;
final out = <String, dynamic>{}; final out = <String, dynamic>{};
for (final e in input.entries) { for (final e in input.entries) {
final key = mapping[e.key] ?? e.key; final key = mapField(e.key);
out[key] = _mapRequestValue(e.value); out[key] = _mapRequestValue(e.value);
} }
return out; return out;
@ -40,10 +46,9 @@ class FieldMapping {
/// Map key /// Map key
Map<String, dynamic> mapResponse(Map<String, dynamic> input) { Map<String, dynamic> mapResponse(Map<String, dynamic> input) {
if (input.isEmpty) return input; if (input.isEmpty) return input;
final inv = _inverse;
final out = <String, dynamic>{}; final out = <String, dynamic>{};
for (final e in input.entries) { for (final e in input.entries) {
final key = inv[e.key] ?? e.key; final key = reverseMapField(e.key);
out[key] = _mapResponseValue(e.value); out[key] = _mapResponseValue(e.value);
} }
return out; return out;
@ -58,4 +63,21 @@ class FieldMapping {
} }
return value; return value;
} }
// === ===
/// code
String get responseCodeField => mapField('code');
/// msg
String get responseMsgField => mapField('msg');
/// data
String get responseDataField => mapField('data');
// === ===
///
String get headerPackageNameField => mapField('pkg');
/// token
String get headerUserTokenField => mapField('User_token');
} }

View File

@ -0,0 +1,5 @@
export 'entity.dart';
export 'feedback_entities.dart';
export 'image_entities.dart';
export 'payment_entities.dart';
export 'user_entities.dart';

View File

@ -0,0 +1,12 @@
///
abstract class Entity {
Entity();
/// Map
factory Entity.fromJson(Map<String, dynamic> json) {
throw UnimplementedError('Subclass must implement fromJson');
}
/// Map
Map<String, dynamic> toJson();
}

View File

@ -0,0 +1,52 @@
import 'entity.dart';
/// URL
class FeedbackUploadPresignedUrlResponse extends Entity {
FeedbackUploadPresignedUrlResponse({
this.uploadUrl,
this.filePath,
});
final String? uploadUrl;
final String? filePath;
@override
factory FeedbackUploadPresignedUrlResponse.fromJson(
Map<String, dynamic> json) {
return FeedbackUploadPresignedUrlResponse(
uploadUrl: json['uploadUrl'] as String?,
filePath: json['filePath'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'uploadUrl': uploadUrl,
'filePath': filePath,
};
}
///
class SubmitFeedbackResponse extends Entity {
SubmitFeedbackResponse({
this.success,
this.feedbackId,
});
final bool? success;
final String? feedbackId;
@override
factory SubmitFeedbackResponse.fromJson(Map<String, dynamic> json) {
return SubmitFeedbackResponse(
success: json['success'] as bool?,
feedbackId: json['feedbackId'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'success': success,
'feedbackId': feedbackId,
};
}

View File

@ -0,0 +1,402 @@
import 'entity.dart';
///
class CategoryItem extends Entity {
CategoryItem({
this.id,
this.name,
this.icon,
});
final int? id;
final String? name;
final String? icon;
@override
factory CategoryItem.fromJson(Map<String, dynamic> json) {
return CategoryItem(
id: json['id'] as int?,
name: json['name'] as String?,
icon: json['icon'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'icon': icon,
};
}
///
class CategoryListResponse extends Entity {
CategoryListResponse({
this.categories,
});
final List<CategoryItem>? categories;
@override
factory CategoryListResponse.fromJson(Map<String, dynamic> json) {
final list = json['categories'] as List<dynamic>?;
return CategoryListResponse(
categories: list
?.map((e) => CategoryItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
@override
Map<String, dynamic> toJson() => {
'categories': categories?.map((e) => e.toJson()).toList(),
};
}
///
class TaskItem extends Entity {
TaskItem({
this.id,
this.name,
this.imageUrl,
this.categoryId,
});
final String? id;
final String? name;
final String? imageUrl;
final int? categoryId;
@override
factory TaskItem.fromJson(Map<String, dynamic> json) {
return TaskItem(
id: json['id'] as String?,
name: json['name'] as String?,
imageUrl: json['imageUrl'] as String?,
categoryId: json['categoryId'] as int?,
);
}
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'imageUrl': imageUrl,
'categoryId': categoryId,
};
}
///
class TasksResponse extends Entity {
TasksResponse({
this.tasks,
});
final List<TaskItem>? tasks;
@override
factory TasksResponse.fromJson(Map<String, dynamic> json) {
final list = json['tasks'] as List<dynamic>?;
return TasksResponse(
tasks: list
?.map((e) => TaskItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
@override
Map<String, dynamic> toJson() => {
'tasks': tasks?.map((e) => e.toJson()).toList(),
};
}
///
class PromptRecommendItem extends Entity {
PromptRecommendItem({
this.prompt,
this.icon,
});
final String? prompt;
final String? icon;
@override
factory PromptRecommendItem.fromJson(Map<String, dynamic> json) {
return PromptRecommendItem(
prompt: json['prompt'] as String?,
icon: json['icon'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'prompt': prompt,
'icon': icon,
};
}
///
class PromptRecommendsResponse extends Entity {
PromptRecommendsResponse({
this.recommends,
});
final List<PromptRecommendItem>? recommends;
@override
factory PromptRecommendsResponse.fromJson(Map<String, dynamic> json) {
final list = json['recommends'] as List<dynamic>?;
return PromptRecommendsResponse(
recommends: list
?.map((e) => PromptRecommendItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
@override
Map<String, dynamic> toJson() => {
'recommends': recommends?.map((e) => e.toJson()).toList(),
};
}
///
class ProgressResponse extends Entity {
ProgressResponse({
this.taskId,
this.status,
this.progress,
this.resultUrl,
});
final String? taskId;
final String? status;
final int? progress;
final String? resultUrl;
@override
factory ProgressResponse.fromJson(Map<String, dynamic> json) {
return ProgressResponse(
taskId: json['taskId'] as String?,
status: json['status'] as String?,
progress: json['progress'] as int?,
resultUrl: json['resultUrl'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'taskId': taskId,
'status': status,
'progress': progress,
'resultUrl': resultUrl,
};
}
/// 姿
class PoseTemplateItem extends Entity {
PoseTemplateItem({
this.id,
this.name,
this.imageUrl,
});
final String? id;
final String? name;
final String? imageUrl;
@override
factory PoseTemplateItem.fromJson(Map<String, dynamic> json) {
return PoseTemplateItem(
id: json['id'] as String?,
name: json['name'] as String?,
imageUrl: json['imageUrl'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'imageUrl': imageUrl,
};
}
/// 姿
class PoseTemplatesResponse extends Entity {
PoseTemplatesResponse({
this.templates,
});
final List<PoseTemplateItem>? templates;
@override
factory PoseTemplatesResponse.fromJson(Map<String, dynamic> json) {
final list = json['templates'] as List<dynamic>?;
return PoseTemplatesResponse(
templates: list
?.map((e) => PoseTemplateItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
@override
Map<String, dynamic> toJson() => {
'templates': templates?.map((e) => e.toJson()).toList(),
};
}
/// URL
class UploadPresignedUrlResponse extends Entity {
UploadPresignedUrlResponse({
this.uploadUrl,
this.filePath,
});
final String? uploadUrl;
final String? filePath;
@override
factory UploadPresignedUrlResponse.fromJson(Map<String, dynamic> json) {
return UploadPresignedUrlResponse(
uploadUrl: json['uploadUrl'] as String?,
filePath: json['filePath'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'uploadUrl': uploadUrl,
'filePath': filePath,
};
}
///
class CreateTaskResponse extends Entity {
CreateTaskResponse({
this.taskId,
this.status,
});
final String? taskId;
final String? status;
@override
factory CreateTaskResponse.fromJson(Map<String, dynamic> json) {
return CreateTaskResponse(
taskId: json['taskId'] as String?,
status: json['status'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'taskId': taskId,
'status': status,
};
}
///
class MyTaskItem extends Entity {
MyTaskItem({
this.taskId,
this.status,
this.progress,
this.resultUrl,
this.createTime,
this.type,
});
final String? taskId;
final String? status;
final int? progress;
final String? resultUrl;
final String? createTime;
final String? type;
@override
factory MyTaskItem.fromJson(Map<String, dynamic> json) {
return MyTaskItem(
taskId: json['taskId'] as String?,
status: json['status'] as String?,
progress: json['progress'] as int?,
resultUrl: json['resultUrl'] as String?,
createTime: json['createTime'] as String?,
type: json['type'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'taskId': taskId,
'status': status,
'progress': progress,
'resultUrl': resultUrl,
'createTime': createTime,
'type': type,
};
}
///
class MyTasksResponse extends Entity {
MyTasksResponse({
this.tasks,
this.total,
this.cursor,
});
final List<MyTaskItem>? tasks;
final int? total;
final String? cursor;
@override
factory MyTasksResponse.fromJson(Map<String, dynamic> json) {
final list = json['tasks'] as List<dynamic>?;
return MyTasksResponse(
tasks: list
?.map((e) => MyTaskItem.fromJson(e as Map<String, dynamic>))
.toList(),
total: json['total'] as int?,
cursor: json['cursor'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'tasks': tasks?.map((e) => e.toJson()).toList(),
'total': total,
'cursor': cursor,
};
}
///
class CreditsPageInfoResponse extends Entity {
CreditsPageInfoResponse({
this.credits,
this.freeTimes,
this.vipExpireTime,
this.isVip,
});
final int? credits;
final int? freeTimes;
final String? vipExpireTime;
final bool? isVip;
@override
factory CreditsPageInfoResponse.fromJson(Map<String, dynamic> json) {
return CreditsPageInfoResponse(
credits: json['credits'] as int?,
freeTimes: json['freeTimes'] as int?,
vipExpireTime: json['vipExpireTime'] as String?,
isVip: json['isVip'] as bool?,
);
}
@override
Map<String, dynamic> toJson() => {
'credits': credits,
'freeTimes': freeTimes,
'vipExpireTime': vipExpireTime,
'isVip': isVip,
};
}

View File

@ -0,0 +1,214 @@
import 'entity.dart';
///
class PaymentProductItem extends Entity {
PaymentProductItem({
this.productId,
this.activityId,
this.actualAmount,
this.originAmount,
this.bonus,
this.title,
});
final String? productId;
final String? activityId;
final String? actualAmount;
final String? originAmount;
final int? bonus;
final String? title;
@override
factory PaymentProductItem.fromJson(Map<String, dynamic> json) {
return PaymentProductItem(
productId: json['productId'] as String?,
activityId: json['activityId'] as String?,
actualAmount: json['actualAmount'] as String?,
originAmount: json['originAmount'] as String?,
bonus: json['bonus'] as int?,
title: json['title'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'productId': productId,
'activityId': activityId,
'actualAmount': actualAmount,
'originAmount': originAmount,
'bonus': bonus,
'title': title,
};
}
///
class PaymentProductsResponse extends Entity {
PaymentProductsResponse({
this.productList,
});
final List<PaymentProductItem>? productList;
@override
factory PaymentProductsResponse.fromJson(Map<String, dynamic> json) {
final list = json['productList'] as List<dynamic>?;
return PaymentProductsResponse(
productList: list
?.map((e) => PaymentProductItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
@override
Map<String, dynamic> toJson() => {
'productList': productList?.map((e) => e.toJson()).toList(),
};
}
///
class PaymentMethodItem extends Entity {
PaymentMethodItem({
this.paymentMethod,
this.subPaymentMethod,
this.name,
this.icon,
this.recommend,
});
final String? paymentMethod;
final String? subPaymentMethod;
final String? name;
final String? icon;
final bool? recommend;
@override
factory PaymentMethodItem.fromJson(Map<String, dynamic> json) {
return PaymentMethodItem(
paymentMethod: json['paymentMethod'] as String?,
subPaymentMethod: json['subPaymentMethod'] as String?,
name: json['name'] as String?,
icon: json['icon'] as String?,
recommend: json['recommend'] as bool?,
);
}
@override
Map<String, dynamic> toJson() => {
'paymentMethod': paymentMethod,
'subPaymentMethod': subPaymentMethod,
'name': name,
'icon': icon,
'recommend': recommend,
};
}
///
class PaymentMethodsResponse extends Entity {
PaymentMethodsResponse({
this.paymentMethods,
});
final List<PaymentMethodItem>? paymentMethods;
@override
factory PaymentMethodsResponse.fromJson(Map<String, dynamic> json) {
final list = json['paymentMethods'] as List<dynamic>?;
return PaymentMethodsResponse(
paymentMethods: list
?.map((e) => PaymentMethodItem.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
@override
Map<String, dynamic> toJson() => {
'paymentMethods': paymentMethods?.map((e) => e.toJson()).toList(),
};
}
///
class CreatePaymentResponse extends Entity {
CreatePaymentResponse({
this.orderId,
this.payUrl,
this.status,
});
final String? orderId;
final String? payUrl;
final String? status;
@override
factory CreatePaymentResponse.fromJson(Map<String, dynamic> json) {
return CreatePaymentResponse(
orderId: json['orderId'] as String?,
payUrl: json['payUrl'] as String?,
status: json['status'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'orderId': orderId,
'payUrl': payUrl,
'status': status,
};
}
///
class OrderDetailResponse extends Entity {
OrderDetailResponse({
this.orderId,
this.status,
this.amount,
});
final String? orderId;
final String? status;
final String? amount;
@override
factory OrderDetailResponse.fromJson(Map<String, dynamic> json) {
return OrderDetailResponse(
orderId: json['orderId'] as String?,
status: json['status'] as String?,
amount: json['amount'] as String?,
);
}
@override
Map<String, dynamic> toJson() => {
'orderId': orderId,
'status': status,
'amount': amount,
};
}
/// Google Pay
class GooglePayCallbackResponse extends Entity {
GooglePayCallbackResponse({
this.orderId,
this.status,
this.creditsAdded,
});
final String? orderId;
final String? status;
final bool? creditsAdded;
@override
factory GooglePayCallbackResponse.fromJson(Map<String, dynamic> json) {
return GooglePayCallbackResponse(
orderId: json['orderId'] as String?,
status: json['status'] as String?,
creditsAdded: json['creditsAdded'] as bool?,
);
}
@override
Map<String, dynamic> toJson() => {
'orderId': orderId,
'status': status,
'creditsAdded': creditsAdded,
};
}

View File

@ -0,0 +1,292 @@
import 'entity.dart';
///
class FastLoginResponse extends Entity {
FastLoginResponse({
this.userToken,
this.userId,
this.credits,
this.avatar,
this.userName,
this.countryCode,
this.extConfig,
this.appFbConfig,
this.usign,
this.creditsRecordUrl,
this.tgId,
this.tgName,
this.email,
this.forcePayCenter,
this.t2IConfig,
this.h5UrlConfig,
this.payCenterUrl,
this.freeBlurTimes,
this.firstRegister,
this.isVip,
this.tags,
this.freeTimes,
this.subScribeValidTime,
});
final String? userToken;
final String? userId;
final int? credits;
final String? avatar;
final String? userName;
final String? countryCode;
final String? extConfig;
final String? appFbConfig;
final String? usign;
final String? creditsRecordUrl;
final String? tgId;
final String? tgName;
final String? email;
final bool? forcePayCenter;
final String? t2IConfig;
final String? h5UrlConfig;
final String? payCenterUrl;
final int? freeBlurTimes;
final bool? firstRegister;
final bool? isVip;
final String? tags;
final int? freeTimes;
final String? subScribeValidTime;
static String? _toString(dynamic value) {
if (value == null) return null;
if (value is String) return value;
if (value is List) return value.join(',');
return value.toString();
}
static int? _toInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
if (value is num) return value.toInt();
if (value is String && value.isNotEmpty) return int.tryParse(value);
return null;
}
@override
factory FastLoginResponse.fromJson(Map<String, dynamic> json) {
return FastLoginResponse(
userToken: _toString(json['userToken']),
userId: _toString(json['userId']),
credits: _toInt(json['credits']),
avatar: _toString(json['avatar']),
userName: _toString(json['userName']),
countryCode: _toString(json['countryCode']),
extConfig: _toString(json['extConfig']),
appFbConfig: _toString(json['appFbConfig']),
usign: _toString(json['usign']),
creditsRecordUrl: _toString(json['creditsRecordUrl']),
tgId: _toString(json['tgId']),
tgName: _toString(json['tgName']),
email: _toString(json['email']),
forcePayCenter: json['forcePayCenter'] is bool
? json['forcePayCenter'] as bool
: null,
t2IConfig: _toString(json['t2IConfig']),
h5UrlConfig: _toString(json['h5UrlConfig']),
payCenterUrl: _toString(json['payCenterUrl']),
freeBlurTimes: _toInt(json['freeBlurTimes']),
firstRegister:
json['firstRegister'] is bool ? json['firstRegister'] as bool : null,
isVip: json['isVip'] is bool ? json['isVip'] as bool : null,
tags: _toString(json['tags']),
freeTimes: _toInt(json['freeTimes']),
subScribeValidTime: _toString(json['subScribeValidTime']),
);
}
@override
Map<String, dynamic> toJson() => {
'userToken': userToken,
'userId': userId,
'credits': credits,
'avatar': avatar,
'userName': userName,
'countryCode': countryCode,
'extConfig': extConfig,
'appFbConfig': appFbConfig,
'usign': usign,
'creditsRecordUrl': creditsRecordUrl,
'tgId': tgId,
'tgName': tgName,
'email': email,
'forcePayCenter': forcePayCenter,
't2IConfig': t2IConfig,
'h5UrlConfig': h5UrlConfig,
'payCenterUrl': payCenterUrl,
'freeBlurTimes': freeBlurTimes,
'firstRegister': firstRegister,
'isVip': isVip,
'tags': tags,
'freeTimes': freeTimes,
'subScribeValidTime': subScribeValidTime,
};
}
///
class CommonInfoResponse extends Entity {
CommonInfoResponse({
this.userToken,
this.userId,
this.credits,
this.avatar,
this.userName,
this.countryCode,
this.extConfig,
this.appFbConfig,
this.usign,
this.creditsRecordUrl,
this.tgId,
this.tgName,
this.email,
this.forcePayCenter,
this.t2IConfig,
this.h5UrlConfig,
this.payCenterUrl,
this.freeBlurTimes,
this.firstRegister,
this.isVip,
this.tags,
this.freeTimes,
this.subScribeValidTime,
});
final String? userToken;
final String? userId;
final int? credits;
final String? avatar;
final String? userName;
final String? countryCode;
final String? extConfig;
final String? appFbConfig;
final String? usign;
final String? creditsRecordUrl;
final String? tgId;
final String? tgName;
final String? email;
final bool? forcePayCenter;
final String? t2IConfig;
final String? h5UrlConfig;
final String? payCenterUrl;
final int? freeBlurTimes;
final bool? firstRegister;
final bool? isVip;
final String? tags;
final int? freeTimes;
final String? subScribeValidTime;
static String? _toString(dynamic value) {
if (value == null) return null;
if (value is String) return value;
if (value is List) return value.join(',');
return value.toString();
}
static int? _toInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
if (value is num) return value.toInt();
if (value is String && value.isNotEmpty) return int.tryParse(value);
return null;
}
@override
factory CommonInfoResponse.fromJson(Map<String, dynamic> json) {
return CommonInfoResponse(
userToken: _toString(json['userToken']),
userId: _toString(json['userId']),
credits: _toInt(json['credits']),
avatar: _toString(json['avatar']),
userName: _toString(json['userName']),
countryCode: _toString(json['countryCode']),
extConfig: _toString(json['extConfig']),
appFbConfig: _toString(json['appFbConfig']),
usign: _toString(json['usign']),
creditsRecordUrl: _toString(json['creditsRecordUrl']),
tgId: _toString(json['tgId']),
tgName: _toString(json['tgName']),
email: _toString(json['email']),
forcePayCenter: json['forcePayCenter'] is bool
? json['forcePayCenter'] as bool
: null,
t2IConfig: _toString(json['t2IConfig']),
h5UrlConfig: _toString(json['h5UrlConfig']),
payCenterUrl: _toString(json['payCenterUrl']),
freeBlurTimes: _toInt(json['freeBlurTimes']),
firstRegister:
json['firstRegister'] is bool ? json['firstRegister'] as bool : null,
isVip: json['isVip'] is bool ? json['isVip'] as bool : null,
tags: _toString(json['tags']),
freeTimes: _toInt(json['freeTimes']),
subScribeValidTime: _toString(json['subScribeValidTime']),
);
}
@override
Map<String, dynamic> toJson() => {
'userToken': userToken,
'userId': userId,
'credits': credits,
'avatar': avatar,
'userName': userName,
'countryCode': countryCode,
'extConfig': extConfig,
'appFbConfig': appFbConfig,
'usign': usign,
'creditsRecordUrl': creditsRecordUrl,
'tgId': tgId,
'tgName': tgName,
'email': email,
'forcePayCenter': forcePayCenter,
't2IConfig': t2IConfig,
'h5UrlConfig': h5UrlConfig,
'payCenterUrl': payCenterUrl,
'freeBlurTimes': freeBlurTimes,
'firstRegister': firstRegister,
'isVip': isVip,
'tags': tags,
'freeTimes': freeTimes,
'subScribeValidTime': subScribeValidTime,
};
}
///
class AccountResponse extends Entity {
AccountResponse({
this.credits,
this.avatar,
this.userName,
this.isVip,
this.freeTimes,
});
final int? credits;
final String? avatar;
final String? userName;
final bool? isVip;
final int? freeTimes;
@override
factory AccountResponse.fromJson(Map<String, dynamic> json) {
return AccountResponse(
credits: json['credits'] as int?,
avatar: json['avatar'] as String?,
userName: json['userName'] as String?,
isVip: json['isVip'] as bool?,
freeTimes: json['freeTimes'] as int?,
);
}
@override
Map<String, dynamic> toJson() => {
'credits': credits,
'avatar': avatar,
'userName': userName,
'isVip': isVip,
'freeTimes': freeTimes,
};
}

View File

@ -1,6 +1,159 @@
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:logger/logger.dart'; import 'package:logger/logger.dart';
final _proxyLog = AppLogger('ProxyClient');
const int _maxLogChunk = 1000;
int _findMatchingBracket(String str, int start, String open) {
final close = open == '{' ? '}' : ']';
int depth = 1;
int i = start + 1;
while (i < str.length) {
final c = str[i];
if (c == '"') {
i = _skipJsonString(str, i);
if (i < 0) return -1;
continue;
}
if (c == open) {
depth++;
} else if (c == close) {
depth--;
if (depth == 0) return i;
}
i++;
}
return -1;
}
int _skipJsonString(String str, int start) {
if (start >= str.length || str[start] != '"') return -1;
int i = start + 1;
while (i < str.length) {
final c = str[i++];
if (c == '\\') {
if (i < str.length) i++;
continue;
}
if (c == '"') return i;
}
return -1;
}
void _logLong(String text) {
if (text.isEmpty) return;
final lines = text.split('\n');
if (lines.length == 1 && text.length <= _maxLogChunk) {
_proxyLog.d(text);
return;
}
final buffer = StringBuffer();
int chunkIndex = 0;
for (final line in lines) {
final lineWithNewline = buffer.isEmpty ? line : '\n$line';
if (buffer.length + lineWithNewline.length > _maxLogChunk &&
buffer.isNotEmpty) {
chunkIndex++;
_proxyLog.d('(part $chunkIndex)\n$buffer');
buffer.clear();
buffer.write(line);
} else {
if (buffer.isNotEmpty) buffer.write('\n');
buffer.write(line);
}
}
if (buffer.isNotEmpty) {
chunkIndex++;
_proxyLog
.d(chunkIndex > 1 ? '(part $chunkIndex)\n$buffer' : buffer.toString());
}
}
/// JSON
void logWithEmbeddedJson(Object? msg) {
if (!kDebugMode) return;
if (msg is! String) {
_proxyLog.d(msg);
return;
}
final String str = msg.trim();
final sb = StringBuffer();
void write(String line) {
sb.writeln(line);
}
void printJson(dynamic value, [int indent = 0]) {
final pad = ' ' * indent;
final padInner = ' ' * (indent + 1);
if (value is Map<String, dynamic>) {
write('$pad{');
value.forEach((k, v) {
if (v is Map || v is List) {
write('$padInner"$k":');
printJson(v, indent + 1);
} else {
write('$padInner"$k": ${json.encode(v)}');
}
});
write('$pad}');
} else if (value is List) {
write('$pad[');
for (final item in value) {
printJson(item, indent + 1);
}
write('$pad]');
} else {
write('$pad${json.encode(value)}');
}
}
int i = 0;
int lastEnd = 0;
while (i < str.length) {
final c = str[i];
if (c == '"') {
final next = _skipJsonString(str, i);
if (next > 0) {
i = next;
continue;
}
i++;
continue;
}
if (c == '{' || c == '[') {
final end = _findMatchingBracket(str, i, c);
if (end >= 0) {
final prefix = str.substring(lastEnd, i).trim();
if (prefix.isNotEmpty) write(prefix);
final jsonStr = str.substring(i, end + 1);
try {
final parsed = json.decode(jsonStr);
printJson(parsed, 0);
} catch (e) {
write(jsonStr);
}
lastEnd = end + 1;
i = end + 1;
continue;
}
}
i++;
}
final trailing = str.substring(lastEnd).trim();
if (trailing.isNotEmpty) write(trailing);
final out = sb.toString().trim();
if (out.isNotEmpty) _logLong(out);
}
/// ///
class AppLogger { class AppLogger {
AppLogger([this.tag = 'App']); AppLogger([this.tag = 'App']);

View File

@ -0,0 +1,207 @@
import 'dart:async';
import 'dart:convert';
import 'package:adjust_sdk/adjust.dart' as adj;
import 'package:adjust_sdk/adjust_attribution.dart';
import 'package:adjust_sdk/adjust_config.dart' as adj;
import 'package:adjust_sdk/adjust_event.dart';
import 'package:flutter/foundation.dart';
import 'package:play_install_referrer/play_install_referrer.dart';
import '../log/app_logger.dart';
typedef AdjustAttributionCallback = void Function(
AdjustAttribution attribution);
class AdjustService {
AdjustService._();
static final _log = AppLogger('Adjust');
static adj.AdjustConfig? _config;
static AdjustAttributionCallback? _attributionCallback;
static String _referrerSource = 'gg';
static String? _cachedReferrer;
static const int _adjustTimeoutMs = 15000;
static final Completer<AdjustAttribution?> _attributionCallbackCompleter =
Completer<AdjustAttribution?>();
static void init(adj.AdjustConfig config,
{AdjustAttributionCallback? onAttribution}) {
_config = config;
_attributionCallback = onAttribution;
config.attributionCallback = _onAttribution;
adj.Adjust.initSdk(config);
_log.d('Adjust initialized');
if (kDebugMode) {
_log.d('Ensure Adjust App Token matches your Adjust dashboard');
}
}
static void _onAttribution(AdjustAttribution attribution) {
if (!_attributionCallbackCompleter.isCompleted) {
_attributionCallbackCompleter.complete(attribution);
}
_attributionCallback?.call(attribution);
_log.d('Attribution: trackerToken=${attribution.trackerToken}, '
'network=${attribution.network}, campaign=${attribution.campaign}');
}
static void trackEvent(String eventToken) {
adj.Adjust.trackEvent(AdjustEvent(eventToken));
_log.d('Track event: $eventToken');
}
static void trackRevenueEvent(
String eventToken, double revenue, String currency) {
final event = AdjustEvent(eventToken);
event.setRevenue(revenue, currency);
adj.Adjust.trackEvent(event);
_log.d('Track revenue event: $eventToken, revenue: $revenue $currency');
}
static Future<AttributionData?> getAttribution() async {
if (_config == null) return null;
// Adjust
try {
final attribution = await Future.any<AdjustAttribution?>([
(() async {
try {
return await adj.Adjust.getAttributionWithTimeout(_adjustTimeoutMs);
} catch (_) {
return null;
}
})(),
_attributionCallbackCompleter.future,
]);
if (attribution != null) {
_referrerSource = 'android_adjust';
// Adjust Google Play referrer
await _getPlayReferrer();
return _toAttributionData(attribution);
}
} catch (_) {}
// Adjust Google Play referrer
await _getPlayReferrer();
return null;
}
static Future<void> _getPlayReferrer() async {
if (defaultTargetPlatform != TargetPlatform.android) return;
try {
final details = await PlayInstallReferrer.installReferrer;
if (details.installReferrer != null &&
details.installReferrer!.isNotEmpty) {
_referrerSource = 'gg';
_cachedReferrer = details.installReferrer;
}
} catch (_) {}
}
static Object? _jsonEncodableCostAmount(num? v) {
if (v == null) return null;
if (v is double && !v.isFinite) return v.toString();
return v;
}
static String _attributionToDigest(AdjustAttribution attr) {
final map = <String, dynamic>{
'trackerToken': attr.trackerToken,
'trackerName': attr.trackerName,
'network': attr.network,
'campaign': attr.campaign,
'adgroup': attr.adgroup,
'creative': attr.creative,
'clickLabel': attr.clickLabel,
'costType': attr.costType,
'costAmount': _jsonEncodableCostAmount(attr.costAmount),
'costCurrency': attr.costCurrency,
'jsonResponse': attr.jsonResponse,
'fbInstallReferrer': attr.fbInstallReferrer,
};
try {
return jsonEncode(map);
} catch (_) {
return '';
}
}
static AttributionData _toAttributionData(AdjustAttribution attr) {
return AttributionData(
trackerToken: attr.trackerToken,
trackerName: attr.trackerName,
network: attr.network,
campaign: attr.campaign,
adgroup: attr.adgroup,
creative: attr.creative,
clickLabel: attr.clickLabel,
costType: attr.costType,
costAmount: attr.costAmount?.toDouble(),
costCurrency: attr.costCurrency,
jsonResponse: attr.jsonResponse,
fbInstallReferrer: attr.fbInstallReferrer,
);
}
static Future<String> getReferrerDigest() async {
try {
final attr = await adj.Adjust.getAttribution();
final raw = _attributionToDigest(attr);
if (raw.isEmpty) return '';
return base64Encode(utf8.encode(raw));
} catch (_) {
return '';
}
}
static Future<String> getPlayReferrerDigest() async {
if (defaultTargetPlatform != TargetPlatform.android) return '';
try {
final details = await PlayInstallReferrer.installReferrer;
return details.installReferrer ?? '';
} catch (_) {
return '';
}
}
static String get referrerSource => _referrerSource;
static String? get cachedPlayReferrer => _cachedReferrer;
}
class AttributionData {
AttributionData({
this.trackerToken,
this.trackerName,
this.network,
this.campaign,
this.adgroup,
this.creative,
this.clickLabel,
this.costType,
this.costAmount,
this.costCurrency,
this.jsonResponse,
this.fbInstallReferrer,
});
final String? trackerToken;
final String? trackerName;
final String? network;
final String? campaign;
final String? adgroup;
final String? creative;
final String? clickLabel;
final String? costType;
final double? costAmount;
final String? costCurrency;
final String? jsonResponse;
final String? fbInstallReferrer;
}

View File

@ -0,0 +1,208 @@
import 'package:adjust_sdk/adjust_config.dart' as adj;
import 'package:adjust_sdk/adjust_attribution.dart'
as adjust_sdk_adjust_attribution;
import 'package:flutter/foundation.dart';
import '../config/attribution_config.dart';
import 'adjust_service.dart';
import 'facebook_service.dart';
typedef AttributionCallback = void Function(AttributionData data);
class AnalyticsConfig {
const AnalyticsConfig({
required this.packageName,
this.adjustConfig,
this.facebookConfig,
this.platformAttributionConfig,
this.debugLogs = false,
});
final String packageName;
final AdjustConfig? adjustConfig;
final FacebookConfig? facebookConfig;
final PlatformAttributionConfig? platformAttributionConfig;
final bool debugLogs;
}
abstract class AnalyticsService {
static AnalyticsConfig? _config;
static AttributionCallback? _attributionCallback;
static bool _isInitialized = false;
static AttributionData? _cachedAttribution;
static Future<void> init(AnalyticsConfig config) async {
_config = config;
_initAdjust();
await _initFacebook();
_isInitialized = true;
}
static void setAttributionCallback(AttributionCallback callback) {
_attributionCallback = callback;
}
static bool get isInitialized => _isInitialized;
///
/// main()
static Future<void> initAttribution() async {
if (_cachedAttribution != null) return;
_cachedAttribution = await AdjustService.getAttribution();
if (_config?.debugLogs ?? false) {
debugPrint(
'[Analytics] Attribution cached: ${_cachedAttribution?.trackerToken ?? "none"}');
}
}
///
static AttributionData? getAttribution() {
return _cachedAttribution;
}
static void _initAdjust() {
final adjustConfig = _config?.adjustConfig;
if (adjustConfig == null) {
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Adjust not configured, skipping');
}
return;
}
try {
final environment = adjustConfig.environment == AdjustEnv.sandbox
? adj.AdjustEnvironment.sandbox
: adj.AdjustEnvironment.production;
final sdkConfig = adj.AdjustConfig(adjustConfig.appToken, environment);
if (adjustConfig.logLevel == AdjustLogLevel.verbose) {
sdkConfig.logLevel = adj.AdjustLogLevel.verbose;
} else {
sdkConfig.logLevel = adj.AdjustLogLevel.suppress;
}
if (adjustConfig.fbAppId != null) {
sdkConfig.fbAppId = adjustConfig.fbAppId;
}
sdkConfig.attributionCallback = _onAdjustAttribution;
AdjustService.init(sdkConfig, onAttribution: _onAdjustAttribution);
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Adjust initialized: ${adjustConfig.appToken}');
}
} catch (e) {
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Adjust init failed: $e');
}
}
}
static void _onAdjustAttribution(
adjust_sdk_adjust_attribution.AdjustAttribution attribution) {
final data = AttributionData(
trackerToken: attribution.trackerToken,
trackerName: attribution.trackerName,
network: attribution.network,
campaign: attribution.campaign,
adgroup: attribution.adgroup,
creative: attribution.creative,
clickLabel: attribution.clickLabel,
costType: attribution.costType,
costAmount: attribution.costAmount?.toDouble(),
costCurrency: attribution.costCurrency,
jsonResponse: attribution.jsonResponse,
fbInstallReferrer: attribution.fbInstallReferrer,
);
_attributionCallback?.call(data);
}
static Future<void> _initFacebook() async {
final fbConfig = _config?.facebookConfig;
final packageName = _config?.packageName;
if (fbConfig == null || packageName == null) {
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook not configured, skipping');
}
return;
}
try {
await FacebookService.init(fbConfig, packageName: packageName);
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook initialized: ${fbConfig.appId}');
}
} catch (e) {
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook init failed: $e');
}
}
}
static void trackEvent(String eventToken) {
try {
AdjustService.trackEvent(eventToken);
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Adjust track: $eventToken');
}
} catch (e) {
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Adjust track error: $e');
}
}
}
static void trackPurchase({
required double amount,
String currency = 'USD',
String? orderId,
}) {
try {
FacebookService.logPurchase(
amount: amount, currency: currency, orderId: orderId);
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook purchase: $amount $currency');
}
} catch (e) {
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook purchase error: $e');
}
}
}
static void trackSubscribe(String planId) {
try {
FacebookService.logSubscribe(planId);
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook subscribe: $planId');
}
} catch (e) {
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook subscribe error: $e');
}
}
}
static void trackRegister() {
try {
FacebookService.logRegister();
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook register');
}
} catch (e) {
if (_config?.debugLogs ?? false) {
debugPrint('[Analytics] Facebook register error: $e');
}
}
}
static String get referrerSource => AdjustService.referrerSource;
static Future<String> getAdjustReferrerDigest() async {
return AdjustService.getReferrerDigest();
}
static Future<String> getPlayReferrerDigest() async {
return AdjustService.getPlayReferrerDigest();
}
}

View File

@ -0,0 +1,245 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../api/api_client.dart';
import '../api/proxy_client.dart';
import '../config/attribution_config.dart';
import '../entities/user_entities.dart';
import 'adjust_service.dart';
import 'user_api.dart';
///
///
abstract class AuthServiceCallbacks {
/// ID
Future<String> getDeviceId();
///
String computeSign(String deviceId);
///
void onLoginSuccess(FastLoginResponse data) {}
///
void onCommonInfoLoaded(CommonInfoResponse data) {}
///
void onLoginFailed(String msg) {}
}
/// APP
///
/// 1.
/// 2.
/// 3.
abstract class FrameworkAuthService {
static AuthServiceCallbacks? _callbacks;
static Future<void>? _loginFuture;
static bool _isInitialized = false;
///
static final ValueNotifier<bool> isLoginComplete = ValueNotifier(false);
/// Future await Future
static Future<void> get loginComplete => _loginFuture ?? Future<void>.value();
///
static void init(AuthServiceCallbacks callbacks) {
_callbacks = callbacks;
_isInitialized = true;
}
///
/// [delaySeconds] 2
/// [maxRetries] 3
/// [retryDelaySeconds] 2
static Future<void> start({
int delaySeconds = 2,
int maxRetries = 3,
int retryDelaySeconds = 2,
}) async {
if (!_isInitialized || _callbacks == null) {
throw StateError(
'AuthService not initialized. Call AuthService.init(callbacks) first.');
}
if (_loginFuture != null) return _loginFuture!;
final completer = Completer<void>();
_loginFuture = completer.future;
if (kDebugMode) {
debugPrint('[AuthService] start: 开始登录流程');
}
try {
await Future<void>.delayed(Duration(seconds: delaySeconds));
final deviceId = await _callbacks!.getDeviceId();
if (kDebugMode) {
debugPrint('[AuthService] start: deviceId=$deviceId');
}
final sign = _callbacks!.computeSign(deviceId);
if (kDebugMode) {
debugPrint('[AuthService] start: sign=$sign');
}
final referer = await AttributionService.getReferrer();
if (kDebugMode && referer != null) {
debugPrint('[AuthService] start: referer=$referer');
}
//
EntityResponse<FastLoginResponse>? res;
for (var i = 0; i < maxRetries; i++) {
if (i > 0) {
if (kDebugMode) {
debugPrint('[AuthService] start: 第 ${i + 1} 次重试...');
}
await Future<void>.delayed(Duration(seconds: retryDelaySeconds));
}
try {
res = await UserApi.fastLogin(
deviceId: deviceId,
sign: sign,
referer: referer ?? '',
);
break;
} catch (e) {
if (kDebugMode) {
debugPrint('[AuthService] start: 第 ${i + 1} 次请求失败: $e');
}
if (i == maxRetries - 1) rethrow;
}
}
if (res == null) {
completer.complete();
return;
}
if (kDebugMode) {
debugPrint('[AuthService] start: 登录结果 code=${res.code} msg=${res.msg}');
}
if (res.isSuccess && res.data != null) {
final loginData = res.data!;
// Token
if (loginData.userToken != null && loginData.userToken!.isNotEmpty) {
ApiClient.instance.setUserToken(loginData.userToken!);
if (kDebugMode) {
debugPrint('[AuthService] start: 已设置 userToken');
}
}
//
_callbacks!.onLoginSuccess(loginData);
//
await _reportReferrersAndLoadCommonInfo(
uid: loginData.userId ?? '',
deviceId: deviceId,
);
} else {
_callbacks!.onLoginFailed(res.msg);
}
} catch (e, st) {
if (kDebugMode) {
debugPrint('[AuthService] start: 异常 $e\n$st');
}
_callbacks!.onLoginFailed(e.toString());
} finally {
if (!completer.isCompleted) {
completer.complete();
isLoginComplete.value = true;
}
}
}
///
static Future<void> _reportReferrersAndLoadCommonInfo({
required String uid,
required String deviceId,
}) async {
if (uid.isEmpty) return;
final config = ApiClient.instance.config;
// Adjust
final adjustReferer = await AttributionService.getAdjustReferrer();
if (adjustReferer != null && adjustReferer.isNotEmpty) {
try {
final rAdjust = await UserApi.referrer(
app: config.appId,
userId: uid,
referer: adjustReferer,
deviceId: deviceId,
type: 'android_adjust',
);
if (kDebugMode) {
debugPrint(
'[AuthService] referrer(android_adjust): ${rAdjust.isSuccess ? "成功" : "失败"}');
}
} catch (e) {
if (kDebugMode) {
debugPrint('[AuthService] referrer(android_adjust): 异常 $e');
}
}
}
// Google Play AdjustService referrer
final playReferrer = AdjustService.cachedPlayReferrer;
if (playReferrer != null && playReferrer.isNotEmpty) {
try {
final rGg = await UserApi.referrer(
app: config.appId,
userId: uid,
referer: playReferrer,
deviceId: deviceId,
type: 'gg',
);
if (kDebugMode) {
debugPrint(
'[AuthService] referrer(gg): ${rGg.isSuccess ? "成功" : "失败"}');
}
} catch (e) {
if (kDebugMode) {
debugPrint('[AuthService] referrer(gg): 异常 $e');
}
}
}
//
try {
final commonRes = await UserApi.getCommonInfo(
app: config.appId,
userId: uid,
);
if (commonRes.isSuccess && commonRes.data != null) {
_callbacks?.onCommonInfoLoaded(commonRes.data!);
if (kDebugMode) {
debugPrint('[AuthService] common_info: 获取成功');
}
} else if (kDebugMode) {
debugPrint(
'[AuthService] common_info: 失败 code=${commonRes.code} msg=${commonRes.msg}');
}
} catch (e) {
if (kDebugMode) {
debugPrint('[AuthService] common_info: 异常 $e');
}
}
}
/// extConfig JSON
static Map<String, dynamic>? parseExtConfig(String? extConfigStr) {
if (extConfigStr == null || extConfigStr.isEmpty) return null;
try {
return json.decode(extConfigStr) as Map<String, dynamic>?;
} catch (_) {
return null;
}
}
}

View File

@ -0,0 +1,72 @@
import 'package:facebook_app_events/facebook_app_events.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import '../config/attribution_config.dart';
import '../log/app_logger.dart';
class FacebookService {
FacebookService._();
static final _log = AppLogger('Facebook');
static final _fb = FacebookAppEvents();
static MethodChannel? _channel;
static bool _debugLogs = false;
static bool _isInitialized = false;
static void Function()? _onInitialized;
static bool get isInitialized => _isInitialized;
static void setOnInitialized(void Function() callback) {
_onInitialized = callback;
}
static Future<void> init(FacebookConfig config,
{required String packageName}) async {
_debugLogs = config.debugLogs;
_log.d('Facebook App Events init with appId: ${config.appId}');
_log.d('FacebookService.init start, packageName: $packageName');
try {
_log.d('Creating MethodChannel...');
_channel = MethodChannel('$packageName/facebook_sdk');
_log.d('Setting method call handler...');
_channel!.setMethodCallHandler((call) async {
_log.d('MethodCall received: ${call.method}');
if (call.method == 'onFacebookSdkInitialized') {
_isInitialized = true;
_log.d('Facebook App Events initialized (from native callback)');
_onInitialized?.call();
}
});
_log.d('Invoking waitForFacebookSdkInit on native...');
await _channel!.invokeMethod('waitForFacebookSdkInit');
_log.d('waitForFacebookSdkInit completed');
} catch (e, st) {
_log.w('FacebookService.init failed: $e\n$st');
}
}
static void logPurchase({
required double amount,
String currency = 'USD',
String? orderId,
}) {
_fb.logPurchase(amount: amount, currency: currency);
}
static void logSubscribe(String planId) {
_fb.logSubscribe(orderId: planId);
}
static void logRegister({String registrationMethod = 'device'}) {
_fb.logCompletedRegistration(registrationMethod: registrationMethod);
}
static void logEvent(String name, {Map<String, dynamic>? parameters}) {
_fb.logEvent(name: name, parameters: parameters);
}
}

View File

@ -0,0 +1,39 @@
import '../api/api_client.dart';
import '../api/proxy_client.dart';
import '../entities/feedback_entities.dart';
/// / API使
abstract final class FeedbackApi {
static ProxyClient get _client => ApiClient.instance.proxy;
/// URL
static Future<EntityResponse<FeedbackUploadPresignedUrlResponse>>
getUploadPresignedUrl({
required String fileName,
}) async {
return _client.requestEntity(
path: '/v1/feedback/upload-presigned-url',
method: 'POST',
entityFactory: FeedbackUploadPresignedUrlResponse.fromJson,
body: {'fileName': fileName},
);
}
///
static Future<EntityResponse<SubmitFeedbackResponse>> submit({
required List<String> fileUrls,
required String content,
required String contentType,
}) async {
return _client.requestEntity(
path: '/v1/feedback/submit',
method: 'POST',
entityFactory: SubmitFeedbackResponse.fromJson,
body: {
'fileUrls': fileUrls,
'content': content,
'contentType': contentType,
},
);
}
}

View File

@ -1,37 +1,43 @@
import '../api/api_client.dart'; import '../api/api_client.dart';
import '../api/api_response.dart';
import '../api/proxy_client.dart'; import '../api/proxy_client.dart';
import '../entities/image_entities.dart';
/// / API使 /// / API使
abstract final class ImageApi { abstract final class ImageApi {
static ProxyClient get _client => ApiClient.instance.proxy; static ProxyClient get _client => ApiClient.instance.proxy;
/// ///
static Future<ApiResponse> getCategoryList() async { static Future<EntityResponse<CategoryListResponse>> getCategoryList() async {
return _client.request( return _client.requestEntity(
path: '/v1/image/img2video/categories', path: '/v1/image/img2video/categories',
method: 'GET', method: 'GET',
entityFactory: CategoryListResponse.fromJson,
); );
} }
/// ///
static Future<ApiResponse> getImg2VideoTasks({int? categoryId}) async { static Future<EntityResponse<TasksResponse>> getImg2VideoTasks({
return _client.request( int? categoryId,
}) async {
return _client.requestEntity(
path: '/v1/image/img2video/tasks', path: '/v1/image/img2video/tasks',
method: 'GET', method: 'GET',
queryParams: categoryId != null ? {'categoryId': categoryId.toString()} : null, entityFactory: TasksResponse.fromJson,
queryParams:
categoryId != null ? {'categoryId': categoryId.toString()} : null,
); );
} }
/// ///
static Future<ApiResponse> getPromptRecommends({ static Future<EntityResponse<PromptRecommendsResponse>> getPromptRecommends({
required String app, required String app,
String? ch, String? ch,
String? userId, String? userId,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/image/prompt/recomends', path: '/v1/image/prompt/recomends',
method: 'GET', method: 'GET',
entityFactory: PromptRecommendsResponse.fromJson,
queryParams: { queryParams: {
'app': app, 'app': app,
if (ch != null) 'ch': ch, if (ch != null) 'ch': ch,
@ -40,15 +46,69 @@ abstract final class ImageApi {
); );
} }
///
static Future<EntityResponse<CreateTaskResponse>> createTxt2Img({
required String app,
required String prompt,
String? ch,
String? userId,
String? quest,
}) async {
return _client.requestEntity(
path: '/v1/image/txt2img_create',
method: 'POST',
entityFactory: CreateTaskResponse.fromJson,
queryParams: {
'app': app,
if (ch != null) 'ch': ch,
if (userId != null) 'userId': userId,
},
body: {
'declaration': 1,
if (quest != null) 'quest': quest,
'prompt': prompt,
},
);
}
/// 姿
static Future<EntityResponse<CreateTaskResponse>> createImg2VideoPose({
required String app,
required String userId,
String? imgUrl,
String? poseId,
String? templateId,
String? notification,
bool? allowance,
String? cosmos,
}) async {
return _client.requestEntity(
path: '/v1/image/img2video_pose_task',
method: 'POST',
entityFactory: CreateTaskResponse.fromJson,
queryParams: {
'app': app,
'userId': userId,
if (imgUrl != null) 'imgUrl': imgUrl,
if (notification != null) 'notification': notification,
if (allowance != null) 'allowance': allowance.toString(),
if (cosmos != null) 'cosmos': cosmos,
if (poseId != null) 'poseId': poseId,
if (templateId != null) 'templateId': templateId,
},
);
}
/// ///
static Future<ApiResponse> getProgress({ static Future<EntityResponse<ProgressResponse>> getProgress({
required String app, required String app,
required String taskId, required String taskId,
String? userId, String? userId,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/image/progress', path: '/v1/image/progress',
method: 'GET', method: 'GET',
entityFactory: ProgressResponse.fromJson,
queryParams: { queryParams: {
'app': app, 'app': app,
'taskId': taskId, 'taskId': taskId,
@ -57,16 +117,39 @@ abstract final class ImageApi {
); );
} }
/// 姿
static Future<EntityResponse<PoseTemplatesResponse>>
getImg2VideoPoseTemplates({
required String app,
String? ch,
String? userId,
int? categoryId,
}) async {
return _client.requestEntity(
path: '/v1/image/img2Video_pose_template',
method: 'GET',
entityFactory: PoseTemplatesResponse.fromJson,
queryParams: {
'app': app,
if (ch != null) 'ch': ch,
if (userId != null) 'userId': userId,
if (categoryId != null) 'categoryId': categoryId.toString(),
},
);
}
/// URL /// URL
static Future<ApiResponse> getUploadPresignedUrl({ static Future<EntityResponse<UploadPresignedUrlResponse>>
getUploadPresignedUrl({
required String fileName1, required String fileName1,
String? fileName2, String? fileName2,
required String contentType, required String contentType,
required int expectedSize, required int expectedSize,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/image/upload-presigned-url', path: '/v1/image/upload-presigned-url',
method: 'POST', method: 'POST',
entityFactory: UploadPresignedUrlResponse.fromJson,
body: { body: {
'fileName1': fileName1, 'fileName1': fileName1,
'fileName2': fileName2 ?? '', 'fileName2': fileName2 ?? '',
@ -77,7 +160,7 @@ abstract final class ImageApi {
} }
/// / /// /
static Future<ApiResponse> createTask({ static Future<EntityResponse<CreateTaskResponse>> createTask({
required String userId, required String userId,
String? resolution, String? resolution,
String? srcImgUrls, String? srcImgUrls,
@ -88,9 +171,10 @@ abstract final class ImageApi {
bool allowance = false, bool allowance = false,
String? ext, String? ext,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/image/create-task', path: '/v1/image/create-task',
method: 'POST', method: 'POST',
entityFactory: CreateTaskResponse.fromJson,
queryParams: {'userId': userId}, queryParams: {'userId': userId},
body: { body: {
if (resolution != null) 'resolution': resolution, if (resolution != null) 'resolution': resolution,
@ -106,15 +190,16 @@ abstract final class ImageApi {
} }
/// ///
static Future<ApiResponse> getMyTasks({ static Future<EntityResponse<MyTasksResponse>> getMyTasks({
required String app, required String app,
String? page, String? page,
String? pageSize, String? pageSize,
String? cursor, String? cursor,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/image/my-tasks', path: '/v1/image/my-tasks',
method: 'GET', method: 'GET',
entityFactory: MyTasksResponse.fromJson,
queryParams: { queryParams: {
'app': app, 'app': app,
if (page != null) 'page': page, if (page != null) 'page': page,
@ -125,14 +210,15 @@ abstract final class ImageApi {
} }
/// ///
static Future<ApiResponse> getCreditsPageInfo({ static Future<EntityResponse<CreditsPageInfoResponse>> getCreditsPageInfo({
required String app, required String app,
String? userId, String? userId,
String? ch, String? ch,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/image/getCreditsPageInfo', path: '/v1/image/getCreditsPageInfo',
method: 'GET', method: 'GET',
entityFactory: CreditsPageInfoResponse.fromJson,
queryParams: { queryParams: {
'app': app, 'app': app,
if (userId != null) 'userId': userId, if (userId != null) 'userId': userId,

View File

@ -1,21 +1,23 @@
import '../api/api_client.dart'; import '../api/api_client.dart';
import '../api/api_response.dart';
import '../api/proxy_client.dart'; import '../api/proxy_client.dart';
import '../entities/payment_entities.dart';
/// API使 /// API使
abstract final class PaymentApi { abstract final class PaymentApi {
static ProxyClient get _client => ApiClient.instance.proxy; static ProxyClient get _client => ApiClient.instance.proxy;
/// Google /// Google Android
static Future<ApiResponse> getGooglePayActivities({ static Future<EntityResponse<PaymentProductsResponse>>
getGooglePayActivities({
String? app, String? app,
String? shield, String? shield,
String? country, String? country,
String? pkg, String? pkg,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/payment/getGooglePayActivities', path: '/v1/payment/getGooglePayActivities',
method: 'GET', method: 'GET',
entityFactory: PaymentProductsResponse.fromJson,
queryParams: { queryParams: {
'app': app ?? ApiClient.instance.config.appId, 'app': app ?? ApiClient.instance.config.appId,
'pkg': pkg ?? ApiClient.instance.config.packageName, 'pkg': pkg ?? ApiClient.instance.config.packageName,
@ -25,18 +27,19 @@ abstract final class PaymentApi {
); );
} }
/// Apple /// Apple iOS
static Future<ApiResponse> getApplePayActivities({ static Future<EntityResponse<PaymentProductsResponse>> getApplePayActivities({
String? app, String? app,
String? shield, String? shield,
String? country, String? country,
String? pkg, String? pkg,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/payment/getApplePayActivities', path: '/v1/payment/getApplePayActivities',
method: 'GET', method: 'GET',
entityFactory: PaymentProductsResponse.fromJson,
queryParams: { queryParams: {
'app': app ?? ApiClient.instance.config.appId, 'app': app ?? 'HAndroid',
'pkg': pkg ?? ApiClient.instance.config.packageName, 'pkg': pkg ?? ApiClient.instance.config.packageName,
if (shield != null) 'shield': shield, if (shield != null) 'shield': shield,
if (country != null) 'country': country, if (country != null) 'country': country,
@ -45,13 +48,14 @@ abstract final class PaymentApi {
} }
/// ///
static Future<ApiResponse> getPaymentMethods({ static Future<EntityResponse<PaymentMethodsResponse>> getPaymentMethods({
required String activityId, required String activityId,
String? country, String? country,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/payment/get-payment-methods', path: '/v1/payment/get-payment-methods',
method: 'POST', method: 'POST',
entityFactory: PaymentMethodsResponse.fromJson,
body: { body: {
'activityId': activityId, 'activityId': activityId,
if (country != null && country.isNotEmpty) 'country': country, if (country != null && country.isNotEmpty) 'country': country,
@ -60,7 +64,7 @@ abstract final class PaymentApi {
} }
/// ///
static Future<ApiResponse> createPayment({ static Future<EntityResponse<CreatePaymentResponse>> createPayment({
required String app, required String app,
required String userId, required String userId,
required String activityId, required String activityId,
@ -69,32 +73,51 @@ abstract final class PaymentApi {
String? lineage, String? lineage,
String? armor, String? armor,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/payment/createPayment', path: '/v1/payment/createPayment',
method: 'POST', method: 'POST',
entityFactory: CreatePaymentResponse.fromJson,
queryParams: {'app': app, 'userId': userId}, queryParams: {'app': app, 'userId': userId},
body: { body: {
'app': app, 'app': app,
'userId': userId, 'userId': userId,
'activityId': activityId, 'activityId': activityId,
'paymentMethod': paymentMethod, 'paymentMethod': paymentMethod,
if (paymentType != null && paymentType.isNotEmpty) 'paymentType': paymentType, if (paymentType != null && paymentType.isNotEmpty)
'paymentType': paymentType,
if (lineage != null) 'lineage': lineage, if (lineage != null) 'lineage': lineage,
if (armor != null) 'armor': armor, if (armor != null) 'armor': armor,
}, },
); );
} }
///
static Future<EntityResponse<OrderDetailResponse>> getOrderDetail({
required String userId,
required String orderId,
}) async {
return _client.requestEntity(
path: '/v1/payment/getOrderDetail',
method: 'GET',
entityFactory: OrderDetailResponse.fromJson,
queryParams: {
'userId': userId,
'orderId': orderId,
},
);
}
/// Google /// Google
static Future<ApiResponse> googlepay({ static Future<EntityResponse<GooglePayCallbackResponse>> googlepay({
required String signature, required String signature,
required String purchaseData, required String purchaseData,
required String orderId, required String orderId,
required String userId, required String userId,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/payment/googlepay', path: '/v1/payment/googlepay',
method: 'POST', method: 'POST',
entityFactory: GooglePayCallbackResponse.fromJson,
queryParams: { queryParams: {
'app': ApiClient.instance.config.appId, 'app': ApiClient.instance.config.appId,
'userId': userId, 'userId': userId,

View File

@ -0,0 +1,362 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/billing_client_wrappers.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../log/app_logger.dart';
class PaymentResult {
PaymentResult({required this.isSuccess, this.msg = ''});
final bool isSuccess;
final String msg;
}
class GooglePayVerificationPayload {
GooglePayVerificationPayload({
required this.purchaseData,
required this.signature,
});
final String purchaseData;
final String signature;
}
class UnacknowledgedGooglePayPurchase {
UnacknowledgedGooglePayPurchase({
required this.orderId,
required this.productId,
required this.payload,
required this.purchaseDetails,
});
final String orderId;
final String productId;
final GooglePayVerificationPayload payload;
final PurchaseDetails purchaseDetails;
}
class GooglePayPurchaseResult {
GooglePayPurchaseResult({
required this.orderId,
required this.payload,
required this.purchaseDetails,
});
final String orderId;
final GooglePayVerificationPayload payload;
final PurchaseDetails purchaseDetails;
}
typedef OrderRecoveryCallback = Future<void> Function(
String federation, String orderId, String productId);
class PaymentService {
PaymentService._();
static final _log = AppLogger('PaymentService');
static const String _kFederationMapKey =
'google_pay_google_order_to_federation';
static final Map<String, PurchaseDetails> _pendingFromStream = {};
static StreamSubscription<List<PurchaseDetails>>? _pendingStreamSub;
static OrderRecoveryCallback? _orderRecoveryCallback;
static void setOrderRecoveryCallback(OrderRecoveryCallback callback) {
_orderRecoveryCallback = callback;
}
static void startPendingPurchaseListener() {
if (defaultTargetPlatform != TargetPlatform.android) return;
if (_pendingStreamSub != null) return;
final iap = InAppPurchase.instance;
_pendingStreamSub =
iap.purchaseStream.listen((List<PurchaseDetails> purchases) {
for (final p in purchases) {
if (p is! GooglePlayPurchaseDetails) continue;
if (!p.pendingCompletePurchase) continue;
final orderId = p.billingClientPurchase.orderId;
if (orderId.isEmpty) continue;
_pendingFromStream[orderId] = p;
_log.d('purchaseStream received pending orderId=$orderId');
}
}, onError: (e) {
_log.w('purchaseStream error: $e');
});
_log.d('Subscribed to purchaseStream');
}
static Future<void> saveFederationForGoogleOrderId(
String googleOrderId, String federation) async {
try {
final prefs = await SharedPreferences.getInstance();
final json = prefs.getString(_kFederationMapKey);
final map = json != null
? Map<String, String>.from((jsonDecode(json) as Map)
.map((k, v) => MapEntry(k.toString(), v.toString())))
: <String, String>{};
map[googleOrderId] = federation;
await prefs.setString(_kFederationMapKey, jsonEncode(map));
} catch (e) {
_log.w('Failed to save federation mapping: $e');
}
}
static Future<String?> getFederationForGoogleOrderId(
String googleOrderId) async {
try {
final prefs = await SharedPreferences.getInstance();
final json = prefs.getString(_kFederationMapKey);
if (json == null) return null;
final map = (jsonDecode(json) as Map)
.map((k, v) => MapEntry(k.toString(), v.toString()));
final v = map[googleOrderId]?.toString();
return (v != null && v.isNotEmpty) ? v : null;
} catch (e) {
_log.w('Failed to read federation mapping: $e');
return null;
}
}
static Future<void> removeFederationForGoogleOrderId(
String googleOrderId) async {
try {
final prefs = await SharedPreferences.getInstance();
final json = prefs.getString(_kFederationMapKey);
if (json == null) return;
final map = Map<String, String>.from((jsonDecode(json) as Map)
.map((k, v) => MapEntry(k.toString(), v.toString())));
map.remove(googleOrderId);
await prefs.setString(_kFederationMapKey, jsonEncode(map));
} catch (e) {
_log.w('Failed to remove federation mapping: $e');
}
}
static Future<List<UnacknowledgedGooglePayPurchase>>
getUnacknowledgedPurchases() async {
if (defaultTargetPlatform != TargetPlatform.android) {
return [];
}
final iap = InAppPurchase.instance;
if (!await iap.isAvailable()) {
_log.w('Billing not available');
return [];
}
try {
startPendingPurchaseListener();
final androidAddition =
iap.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
final response = await androidAddition.queryPastPurchases();
if (response.error != null) {
_log.w('queryPastPurchases error: ${response.error!.message}');
return [];
}
final list = <UnacknowledgedGooglePayPurchase>[];
final orderIdsFromQuery = <String>{};
for (final p in response.pastPurchases) {
final b = p.billingClientPurchase;
orderIdsFromQuery.add(b.orderId);
list.add(UnacknowledgedGooglePayPurchase(
orderId: b.orderId,
productId: p.productID,
payload: GooglePayVerificationPayload(
purchaseData: b.originalJson,
signature: b.signature,
),
purchaseDetails: p,
));
}
await Future<void>.delayed(const Duration(milliseconds: 1500));
for (final entry in _pendingFromStream.entries) {
if (orderIdsFromQuery.contains(entry.key)) continue;
final p = entry.value;
if (p is! GooglePlayPurchaseDetails) continue;
final b = p.billingClientPurchase;
list.add(UnacknowledgedGooglePayPurchase(
orderId: b.orderId,
productId: p.productID,
payload: GooglePayVerificationPayload(
purchaseData: b.originalJson,
signature: b.signature,
),
purchaseDetails: p,
));
}
_log.d('Found ${list.length} unacknowledged purchases');
return list;
} catch (e, st) {
_log.w('Failed to get unacknowledged purchases: $e\n$st');
return [];
}
}
static Future<bool> completeAndConsumePurchase(
PurchaseDetails purchaseDetails) async {
final iap = InAppPurchase.instance;
try {
iap.completePurchase(purchaseDetails);
_log.d('completePurchase executed');
if (defaultTargetPlatform == TargetPlatform.android) {
final androidAddition =
iap.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
final result = await androidAddition.consumePurchase(purchaseDetails);
final ok = result.responseCode == BillingResponse.ok;
if (ok) {
_log.d('consumePurchase executed');
} else {
_log.w('consumePurchase failed: ${result.responseCode}');
}
return ok;
}
return true;
} catch (e, st) {
_log.w('completePurchase/consumePurchase exception: $e\n$st');
return false;
}
}
static Future<bool> runOrderRecovery({
required String userId,
required Future<PaymentResult> Function(
String federation, String sample, String merchant, String asset)
onPaymentCallback,
}) async {
if (defaultTargetPlatform != TargetPlatform.android) return false;
if (userId.isEmpty) {
_log.d('Order recovery skipped: not logged in');
return false;
}
final pending = await getUnacknowledgedPurchases();
if (pending.isEmpty) return false;
_log.d('Order recovery: ${pending.length} pending');
bool needRefresh = false;
final iap = InAppPurchase.instance;
for (final p in pending) {
try {
final federation = await getFederationForGoogleOrderId(p.orderId);
if (federation != null && federation.isNotEmpty) {
final res = await onPaymentCallback(
federation,
p.payload.signature,
p.payload.purchaseData,
userId,
);
if (res.isSuccess) {
if (await completeAndConsumePurchase(p.purchaseDetails)) {
_pendingFromStream.remove(p.orderId);
await removeFederationForGoogleOrderId(p.orderId);
needRefresh = true;
_log.d('Order recovery success: ${p.orderId}');
}
} else {
_log.w('Order recovery failed: ${p.orderId}, ${res.msg}');
}
} else {
if (await completeAndConsumePurchase(p.purchaseDetails)) {
_pendingFromStream.remove(p.orderId);
needRefresh = true;
}
}
} catch (e, st) {
_log.w('Order recovery exception: ${p.orderId}: $e\n$st');
}
}
return needRefresh;
}
static Future<GooglePayPurchaseResult?> launchPurchaseAndReturnData(
String productId) async {
_log.d('Purchase request for productId: "$productId"');
if (defaultTargetPlatform != TargetPlatform.android) {
return null;
}
final iap = InAppPurchase.instance;
if (!await iap.isAvailable()) {
_log.w('Billing not available');
return null;
}
final response = await iap.queryProductDetails({productId});
if (response.notFoundIDs.isNotEmpty || response.productDetails.isEmpty) {
_log.w('Product not found: $productId');
return null;
}
final product = response.productDetails.first;
final completer = Completer<GooglePayPurchaseResult?>();
StreamSubscription<List<PurchaseDetails>>? sub;
sub = iap.purchaseStream.listen(
(purchases) {
for (final p in purchases) {
if (p.productID != productId) continue;
if (p.status == PurchaseStatus.purchased ||
p.status == PurchaseStatus.restored) {
if (!completer.isCompleted && p is GooglePlayPurchaseDetails) {
final b = p.billingClientPurchase;
_pendingFromStream[b.orderId] = p;
completer.complete(GooglePayPurchaseResult(
orderId: b.orderId,
payload: GooglePayVerificationPayload(
purchaseData: b.originalJson,
signature: b.signature,
),
purchaseDetails: p,
));
}
sub?.cancel();
return;
}
if (p.status == PurchaseStatus.error ||
p.status == PurchaseStatus.canceled) {
if (!completer.isCompleted) completer.complete(null);
sub?.cancel();
return;
}
}
},
onError: (e) {
if (!completer.isCompleted) completer.complete(null);
sub?.cancel();
},
);
final success = await iap.buyConsumable(
purchaseParam: PurchaseParam(productDetails: product),
autoConsume: false,
);
if (!success) {
sub?.cancel();
return null;
}
return completer.future.timeout(
const Duration(seconds: 120),
onTimeout: () {
sub?.cancel();
return null;
},
);
}
static Future<bool> isAvailable() async {
final iap = InAppPurchase.instance;
return iap.isAvailable();
}
static Future<List<ProductDetails>> queryProductDetails(
Set<String> productIds) async {
final iap = InAppPurchase.instance;
final response = await iap.queryProductDetails(productIds);
return response.productDetails;
}
}

View File

@ -1,23 +1,24 @@
import '../api/api_client.dart'; import '../api/api_client.dart';
import '../api/api_response.dart'; import '../api/api_response.dart';
import '../api/proxy_client.dart'; import '../api/proxy_client.dart';
import '../entities/user_entities.dart';
/// API使 /// API使
abstract final class UserApi { abstract final class UserApi {
static ProxyClient get _client => ApiClient.instance.proxy; static ProxyClient get _client => ApiClient.instance.proxy;
/// ///
/// deviceId, sign(MD5(deviceId)), referer() static Future<EntityResponse<FastLoginResponse>> fastLogin({
static Future<ApiResponse> fastLogin({
required String deviceId, required String deviceId,
required String sign, required String sign,
String? referer, String? referer,
String? ch, String? ch,
String? type, String? type,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/user/fast_login', path: '/v1/user/fast_login',
method: 'POST', method: 'POST',
entityFactory: FastLoginResponse.fromJson,
queryParams: { queryParams: {
if (ch != null) 'ch': ch, if (ch != null) 'ch': ch,
'pkg': ApiClient.instance.config.packageName, 'pkg': ApiClient.instance.config.packageName,
@ -57,7 +58,7 @@ abstract final class UserApi {
} }
/// ///
static Future<ApiResponse> getCommonInfo({ static Future<EntityResponse<CommonInfoResponse>> getCommonInfo({
required String app, required String app,
String? shield, String? shield,
String? userId, String? userId,
@ -67,9 +68,10 @@ abstract final class UserApi {
String? gauntlet, String? gauntlet,
String? pkg, String? pkg,
}) async { }) async {
return _client.request( return _client.requestEntity(
path: '/v1/user/common_info', path: '/v1/user/common_info',
method: 'GET', method: 'GET',
entityFactory: CommonInfoResponse.fromJson,
queryParams: { queryParams: {
'app': app, 'app': app,
if (shield != null) 'shield': shield, if (shield != null) 'shield': shield,
@ -84,12 +86,28 @@ abstract final class UserApi {
} }
/// ///
static Future<ApiResponse> getAccount({ static Future<EntityResponse<AccountResponse>> getAccount({
required String app,
String? userId,
}) async {
return _client.requestEntity(
path: '/v1/user/account',
method: 'GET',
entityFactory: AccountResponse.fromJson,
queryParams: {
'app': app,
if (userId != null) 'userId': userId,
},
);
}
///
static Future<ApiResponse> deleteAccount({
required String app, required String app,
String? userId, String? userId,
}) async { }) async {
return _client.request( return _client.request(
path: '/v1/user/account', path: '/v1/user/delete',
method: 'GET', method: 'GET',
queryParams: { queryParams: {
'app': app, 'app': app,

View File

@ -1,6 +1,14 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
adjust_sdk:
dependency: "direct main"
description:
name: adjust_sdk
sha256: "62408962d8b01e0631f4d83ca7300fa43ea7d3b95ad9784fe7a8477e016987e1"
url: "https://pub.dev"
source: hosted
version: "5.5.1"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -73,11 +81,40 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.3" version: "5.0.3"
facebook_app_events:
dependency: "direct main"
description:
name: facebook_app_events
sha256: "0e183947c222e153ab70b9d93c08da18800c73019b9455f86bf2a0bfff30d51e"
url: "https://pub.dev"
source: hosted
version: "0.26.0"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@ -94,6 +131,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
in_app_purchase:
dependency: "direct main"
description:
name: in_app_purchase
sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c"
url: "https://pub.dev"
source: hosted
version: "3.2.3"
in_app_purchase_android:
dependency: "direct main"
description:
name: in_app_purchase_android
sha256: abb254ae159a5a9d4f867795ecb076864faeba59ce015ab81d4cca380f23df45
url: "https://pub.dev"
source: hosted
version: "0.4.0+8"
in_app_purchase_platform_interface:
dependency: transitive
description:
name: in_app_purchase_platform_interface
sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
in_app_purchase_storekit:
dependency: transitive
description:
name: in_app_purchase_storekit
sha256: "1d512809edd9f12ff88fce4596a13a18134e2499013f4d6a8894b04699363c93"
url: "https://pub.dev"
source: hosted
version: "0.4.8+1"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -102,6 +171,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.2" version: "0.7.2"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8
url: "https://pub.dev"
source: hosted
version: "4.11.0"
logger: logger:
dependency: "direct main" dependency: "direct main"
description: description:
@ -134,6 +211,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
play_install_referrer:
dependency: "direct main"
description:
name: play_install_referrer
sha256: "7dd808236d35d15199d5e7a5b55488e4343c1b67c4694902d6b95196b22b19cd"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle: pointycastle:
dependency: transitive dependency: transitive
description: description:
@ -142,6 +267,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.9.1" version: "3.9.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8374d6200ab33ac99031a852eba4c8eb2170c4bf20778b3e2c9eccb45384fb41"
url: "https://pub.dev"
source: hosted
version: "2.4.21"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -195,5 +376,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
sdks: sdks:
dart: ">=3.9.0-0 <4.0.0" dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0"

View File

@ -13,3 +13,9 @@ dependencies:
encrypt: ^5.0.3 encrypt: ^5.0.3
crypto: ^3.0.3 crypto: ^3.0.3
logger: ^2.0.2 logger: ^2.0.2
adjust_sdk: ^5.5.1
facebook_app_events: ^0.26.0
in_app_purchase: ^3.2.0
in_app_purchase_android: ^0.4.0+8
play_install_referrer: ^0.5.0
shared_preferences: ^2.2.2