优化:跑通初步流程
This commit is contained in:
parent
7b8ab4936d
commit
c6449734f9
@ -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": "../",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
1
.flutter-plugins-dependencies
Normal file
1
.flutter-plugins-dependencies
Normal 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
707
docs/README.md
Normal 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
|
||||||
|
```
|
||||||
186
docs/new_app_config_template.md
Normal file
186
docs/new_app_config_template.md
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
# 新应用配置模板
|
||||||
|
|
||||||
|
创建新应用时,请填写以下配置信息。
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
## 一、应用基本信息
|
||||||
|
|
||||||
|
| 配置项 | 值 | 说明 |
|
||||||
|
| ------------- | ------------------ | ------------------- |
|
||||||
|
| 应用名称 | `填写应用名称` | 如:FunyMee AI |
|
||||||
|
| Android 包名 | `填写 Android 包名` | 如:com.funymeeai.app |
|
||||||
|
| iOS Bundle ID | `填写 iOS Bundle ID` | 如:com.funymeeai.app |
|
||||||
|
| 应用 ID(appId) | `填写 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-128:16 字节(16 个字符)
|
||||||
|
> - AES-256:32 字节(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',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
**填写完成后发送给我,我将根据此配置创建完整的应用代码。**
|
||||||
@ -14,13 +14,13 @@
|
|||||||
├─ enableThirdPartyPayment === true 且已登录
|
├─ enableThirdPartyPayment === true 且已登录
|
||||||
│ │
|
│ │
|
||||||
│ ├─ getPaymentMethods(activityId) 获取支付方式
|
│ ├─ getPaymentMethods(activityId) 获取支付方式
|
||||||
│ ├─ 弹窗选择支付方式(_PaymentMethodDialog)
|
│ ├─ 弹窗选择支付方式
|
||||||
│ ├─ createPayment 创建订单
|
│ ├─ createPayment 创建订单
|
||||||
│ │
|
│ │
|
||||||
│ ├─ 若选中的是 Google Pay(resource/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 秒
|
||||||
|
|||||||
317
docs/sdk_integration_guide.md
Normal file
317
docs/sdk_integration_guide.md
Normal 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
|
||||||
869
docs/skin_app_development_guide.md
Normal file
869
docs/skin_app_development_guide.md
Normal file
@ -0,0 +1,869 @@
|
|||||||
|
# 换皮应用开发完整流程指南
|
||||||
|
|
||||||
|
本文档说明如何使用 `client_proxy_framework` 从零创建并完成一个换皮应用。
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
> **重要说明**:本指南**仅完成数据框架的对接**,包括:
|
||||||
|
>
|
||||||
|
> - API 请求封装与字段映射
|
||||||
|
> - 第三方 SDK(Adjust、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` | |
|
||||||
|
| 应用 ID(appId) | `填写 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/` - 主题配置
|
||||||
|
|
||||||
@ -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';
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@ -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 字段名不同,覆盖此方法返回自己的映射
|
||||||
|
|||||||
90
lib/src/config/attribution_config.dart
Normal file
90
lib/src/config/attribution_config.dart
Normal 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();
|
||||||
|
}
|
||||||
@ -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',
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
5
lib/src/entities/entities.dart
Normal file
5
lib/src/entities/entities.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export 'entity.dart';
|
||||||
|
export 'feedback_entities.dart';
|
||||||
|
export 'image_entities.dart';
|
||||||
|
export 'payment_entities.dart';
|
||||||
|
export 'user_entities.dart';
|
||||||
12
lib/src/entities/entity.dart
Normal file
12
lib/src/entities/entity.dart
Normal 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();
|
||||||
|
}
|
||||||
52
lib/src/entities/feedback_entities.dart
Normal file
52
lib/src/entities/feedback_entities.dart
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
402
lib/src/entities/image_entities.dart
Normal file
402
lib/src/entities/image_entities.dart
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
214
lib/src/entities/payment_entities.dart
Normal file
214
lib/src/entities/payment_entities.dart
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
292
lib/src/entities/user_entities.dart
Normal file
292
lib/src/entities/user_entities.dart
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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']);
|
||||||
|
|||||||
207
lib/src/services/adjust_service.dart
Normal file
207
lib/src/services/adjust_service.dart
Normal 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;
|
||||||
|
}
|
||||||
208
lib/src/services/analytics_service.dart
Normal file
208
lib/src/services/analytics_service.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
245
lib/src/services/auth_service.dart
Normal file
245
lib/src/services/auth_service.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
lib/src/services/facebook_service.dart
Normal file
72
lib/src/services/facebook_service.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
lib/src/services/feedback_api.dart
Normal file
39
lib/src/services/feedback_api.dart
Normal 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,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
362
lib/src/services/payment_service.dart
Normal file
362
lib/src/services/payment_service.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
|||||||
192
pubspec.lock
192
pubspec.lock
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user