Compare commits
10 Commits
android-no
...
main
Author | SHA1 | Date | |
---|---|---|---|
9c223bd2d1 | |||
5fc3edfbe4 | |||
def8b49505 | |||
ab042840b9 | |||
311e4fe06c | |||
5c00e4efef | |||
f22c893932 | |||
bd65038cfc | |||
8932eefb14 | |||
ab2e1a49f9 |
15
README.md
@ -47,20 +47,19 @@ keytool -genkeypair -v -keystore kinetra_adehok_app.jks -keyalg RSA -keysize 204
|
|||||||
keytool -list -v -keystore kinetra_adehok_app.jks
|
keytool -list -v -keystore kinetra_adehok_app.jks
|
||||||
|
|
||||||
别名: com.kinetra.adehok.app
|
别名: com.kinetra.adehok.app
|
||||||
创建日期: 2025年8月20日
|
创建日期: 2025年9月22日
|
||||||
条目类型: PrivateKeyEntry
|
条目类型: PrivateKeyEntry
|
||||||
证书链长度: 1
|
证书链长度: 1
|
||||||
证书[1]:
|
证书[1]:
|
||||||
所有者: CN=zy, OU=qj, O=qj, L=cs, ST=hn, C=cn
|
所有者: CN=qj, OU=qj, O=qj, L=cs, ST=hunan, C=cn
|
||||||
发布者: CN=zy, OU=qj, O=qj, L=cs, ST=hn, C=cn
|
发布者: CN=qj, OU=qj, O=qj, L=cs, ST=hunan, C=cn
|
||||||
序列号: 45fb9110ec85ed35
|
序列号: f98a0bc4e952426a
|
||||||
生效时间: Wed Aug 20 13:44:03 CST 2025, 失效时间: Sun Jan 05 13:44:03 CST 2053
|
生效时间: Mon Sep 22 16:02:03 CST 2025, 失效时间: Fri Feb 07 16:02:03 CST 2053
|
||||||
证书指纹:
|
证书指纹:
|
||||||
SHA1: E8:3F:DB:87:73:40:A6:0E:BA:43:C5:C5:42:62:D2:95:FA:E0:DA:21
|
SHA1: 73:61:84:4F:97:9C:EC:B6:5C:25:64:E9:98:51:2C:2E:67:07:1F:AC
|
||||||
SHA256: C5:0A:88:AA:B8:1E:C2:0A:6C:64:0E:99:18:4E:97:86:A0:C8:3F:2C:98:EE:E9:97:6A:A2:CC:B0:03:70:EE:56
|
SHA256: 73:17:E7:6B:6A:7A:E8:32:72:AE:60:A2:18:78:55:2C:8D:49:66:E3:3C:E9:7E:D6:F3:88:6A:FD:F5:50:33:C1
|
||||||
签名算法名称: SHA384withRSA
|
签名算法名称: SHA384withRSA
|
||||||
主体公共密钥算法: 2048 位 RSA 密钥
|
主体公共密钥算法: 2048 位 RSA 密钥
|
||||||
版本: 3
|
|
||||||
|
|
||||||
# 监听model修改,自动生成
|
# 监听model修改,自动生成
|
||||||
dart run build_runner watch --delete-conflicting-outputs
|
dart run build_runner watch --delete-conflicting-outputs
|
||||||
|
BIN
assets/ic_lock.png
Normal file
After Width: | Height: | Size: 853 B |
BIN
assets/ic_order_record.png
Normal file
After Width: | Height: | Size: 830 B |
BIN
assets/ic_polygon.png
Normal file
After Width: | Height: | Size: 379 B |
BIN
assets/lock_black.png
Normal file
After Width: | Height: | Size: 671 B |
BIN
assets/lock_white.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/recharge_bg.png
Normal file
After Width: | Height: | Size: 431 KiB |
BIN
assets/unlock_bg.png
Normal file
After Width: | Height: | Size: 89 KiB |
@ -9,6 +9,7 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
002E1F1EA059FEE73E55E03C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1AEBA8280B01EA417C5A4C5 /* Pods_Runner.framework */; };
|
002E1F1EA059FEE73E55E03C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D1AEBA8280B01EA417C5A4C5 /* Pods_Runner.framework */; };
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
2E31D4A42E82814000481906 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2E31D4A32E82814000481906 /* StoreKit.framework */; };
|
||||||
2F3E2165998010C253A6A3AB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F0B6F62A10C414CCB11B56 /* Pods_RunnerTests.framework */; };
|
2F3E2165998010C253A6A3AB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31F0B6F62A10C414CCB11B56 /* Pods_RunnerTests.framework */; };
|
||||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
@ -44,6 +45,7 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
2E31D4A32E82814000481906 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
|
||||||
31F0B6F62A10C414CCB11B56 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
31F0B6F62A10C414CCB11B56 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -73,6 +75,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
002E1F1EA059FEE73E55E03C /* Pods_Runner.framework in Frameworks */,
|
002E1F1EA059FEE73E55E03C /* Pods_Runner.framework in Frameworks */,
|
||||||
|
2E31D4A42E82814000481906 /* StoreKit.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -145,6 +148,7 @@
|
|||||||
A643DB2437D6E4CE3EFAE3C0 /* Frameworks */ = {
|
A643DB2437D6E4CE3EFAE3C0 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
2E31D4A32E82814000481906 /* StoreKit.framework */,
|
||||||
D1AEBA8280B01EA417C5A4C5 /* Pods_Runner.framework */,
|
D1AEBA8280B01EA417C5A4C5 /* Pods_Runner.framework */,
|
||||||
31F0B6F62A10C414CCB11B56 /* Pods_RunnerTests.framework */,
|
31F0B6F62A10C414CCB11B56 /* Pods_RunnerTests.framework */,
|
||||||
);
|
);
|
||||||
|
@ -93,7 +93,7 @@ class KtApis {
|
|||||||
static String WEB_SITE_FEEDBACK_DETAIL = "${WEB_SITE_HOST}pages/leave/detail";
|
static String WEB_SITE_FEEDBACK_DETAIL = "${WEB_SITE_HOST}pages/leave/detail";
|
||||||
|
|
||||||
///钱包
|
///钱包
|
||||||
static String WEB_SITE_WALLET = "${WEB_SITE_HOST}pages/leave/detail";
|
static String WEB_SITE_WALLET = "${WEB_SITE_HOST}pages/order/kinetra";
|
||||||
|
|
||||||
///搜索
|
///搜索
|
||||||
static String WEB_SITE_SEARCH = "${WEB_SITE_HOST}pages/search/kinetra";
|
static String WEB_SITE_SEARCH = "${WEB_SITE_HOST}pages/search/kinetra";
|
||||||
|
709
lib/kt_model/kt_goods_bean.dart
Normal file
@ -0,0 +1,709 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
|
|
||||||
|
/// id : 93
|
||||||
|
/// status : "enable"
|
||||||
|
/// price : "59.99"
|
||||||
|
/// coins : 6000
|
||||||
|
/// send_coins : 6000
|
||||||
|
/// buy_type : "coins"
|
||||||
|
/// sort : 12
|
||||||
|
/// description : ""
|
||||||
|
/// vip_type : ""
|
||||||
|
/// title : "59.99 for coins"
|
||||||
|
/// brief : ""
|
||||||
|
/// created_at : "2025-04-15 07:01:47"
|
||||||
|
/// origin_price : "0.00"
|
||||||
|
/// backhaul_price : "59.99"
|
||||||
|
/// ios_template_id : "coins_59.99"
|
||||||
|
/// android_template_id : "coins_59.99"
|
||||||
|
/// currency : "US$"
|
||||||
|
/// updated_at : "2025-07-02 06:48:47"
|
||||||
|
/// translate_key : ""
|
||||||
|
/// platform : "all"
|
||||||
|
/// lang_id : 0
|
||||||
|
/// corner_marker : "Popularity"
|
||||||
|
/// first_promotion_price : "0.00"
|
||||||
|
/// version : 2
|
||||||
|
/// user_level : "high"
|
||||||
|
/// vip_days : 0
|
||||||
|
/// user_promise_level : ""
|
||||||
|
/// use_once : 0
|
||||||
|
/// send_coin_ttl : 1
|
||||||
|
/// backhaul_percent : 100
|
||||||
|
/// pay_template_id : 2
|
||||||
|
/// deleted_at : ""
|
||||||
|
/// pay_product_id : 14
|
||||||
|
/// ext_info : "{\"size\": \"big\", \"backhaul_percent_conf\": []}"
|
||||||
|
/// factor : "1.00"
|
||||||
|
/// short_type : ""
|
||||||
|
/// vip_type_key : ""
|
||||||
|
/// auto_sub : "Automatic renewal, cancel at any time"
|
||||||
|
/// size : "big"
|
||||||
|
|
||||||
|
KtGoodsBean ktGoodsBeanFromJson(String str) => KtGoodsBean.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String ktGoodsBeanToJson(KtGoodsBean data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
class KtGoodsBean {
|
||||||
|
KtGoodsBean({
|
||||||
|
int? id,
|
||||||
|
String? status,
|
||||||
|
String? price,
|
||||||
|
int? coins,
|
||||||
|
int? sendCoins,
|
||||||
|
String? buyType,
|
||||||
|
int? sort,
|
||||||
|
String? description,
|
||||||
|
String? vipType,
|
||||||
|
String? title,
|
||||||
|
String? brief,
|
||||||
|
String? createdAt,
|
||||||
|
String? originPrice,
|
||||||
|
String? backhaulPrice,
|
||||||
|
String? iosTemplateId,
|
||||||
|
String? androidTemplateId,
|
||||||
|
String? currency,
|
||||||
|
String? updatedAt,
|
||||||
|
String? translateKey,
|
||||||
|
String? platform,
|
||||||
|
int? langId,
|
||||||
|
String? cornerMarker,
|
||||||
|
String? firstPromotionPrice,
|
||||||
|
int? version,
|
||||||
|
String? userLevel,
|
||||||
|
int? vipDays,
|
||||||
|
String? userPromiseLevel,
|
||||||
|
int? useOnce,
|
||||||
|
int? sendCoinTtl,
|
||||||
|
int? backhaulPercent,
|
||||||
|
int? payTemplateId,
|
||||||
|
String? deletedAt,
|
||||||
|
int? payProductId,
|
||||||
|
ExtInfo? extInfo,
|
||||||
|
String? factor,
|
||||||
|
String? shortType,
|
||||||
|
String? vipTypeKey,
|
||||||
|
String? autoSub,
|
||||||
|
String? size,
|
||||||
|
String? orderCode,
|
||||||
|
String? transactionId,
|
||||||
|
String? serverVerificationData,
|
||||||
|
ProductDetails? productDetails,
|
||||||
|
}) {
|
||||||
|
_id = id;
|
||||||
|
_status = status;
|
||||||
|
_price = price;
|
||||||
|
_coins = coins;
|
||||||
|
_sendCoins = sendCoins;
|
||||||
|
_buyType = buyType;
|
||||||
|
_sort = sort;
|
||||||
|
_description = description;
|
||||||
|
_vipType = vipType;
|
||||||
|
_title = title;
|
||||||
|
_brief = brief;
|
||||||
|
_createdAt = createdAt;
|
||||||
|
_originPrice = originPrice;
|
||||||
|
_backhaulPrice = backhaulPrice;
|
||||||
|
_iosTemplateId = iosTemplateId;
|
||||||
|
_androidTemplateId = androidTemplateId;
|
||||||
|
_currency = currency;
|
||||||
|
_updatedAt = updatedAt;
|
||||||
|
_translateKey = translateKey;
|
||||||
|
_platform = platform;
|
||||||
|
_langId = langId;
|
||||||
|
_cornerMarker = cornerMarker;
|
||||||
|
_firstPromotionPrice = firstPromotionPrice;
|
||||||
|
_version = version;
|
||||||
|
_userLevel = userLevel;
|
||||||
|
_vipDays = vipDays;
|
||||||
|
_userPromiseLevel = userPromiseLevel;
|
||||||
|
_useOnce = useOnce;
|
||||||
|
_sendCoinTtl = sendCoinTtl;
|
||||||
|
_backhaulPercent = backhaulPercent;
|
||||||
|
_payTemplateId = payTemplateId;
|
||||||
|
_deletedAt = deletedAt;
|
||||||
|
_payProductId = payProductId;
|
||||||
|
_extInfo = extInfo;
|
||||||
|
_factor = factor;
|
||||||
|
_shortType = shortType;
|
||||||
|
_vipTypeKey = vipTypeKey;
|
||||||
|
_autoSub = autoSub;
|
||||||
|
_size = size;
|
||||||
|
_orderCode = orderCode;
|
||||||
|
_transactionId = transactionId;
|
||||||
|
_serverVerificationData = serverVerificationData;
|
||||||
|
_productDetails = productDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
KtGoodsBean.fromJson(dynamic json) {
|
||||||
|
_id = json['id'];
|
||||||
|
_status = json['status'];
|
||||||
|
_price = json['price'];
|
||||||
|
_coins = json['coins'];
|
||||||
|
_sendCoins = json['send_coins'];
|
||||||
|
_buyType = json['buy_type'];
|
||||||
|
_sort = json['sort'];
|
||||||
|
_description = json['description'];
|
||||||
|
_vipType = json['vip_type'];
|
||||||
|
_title = json['title'];
|
||||||
|
_brief = json['brief'];
|
||||||
|
_createdAt = json['created_at'];
|
||||||
|
_originPrice = json['origin_price'];
|
||||||
|
_backhaulPrice = json['backhaul_price'];
|
||||||
|
_iosTemplateId = json['ios_template_id'];
|
||||||
|
_androidTemplateId = json['android_template_id'];
|
||||||
|
_currency = json['currency'];
|
||||||
|
_updatedAt = json['updated_at'];
|
||||||
|
_translateKey = json['translate_key'];
|
||||||
|
_platform = json['platform'];
|
||||||
|
_langId = json['lang_id'];
|
||||||
|
_cornerMarker = json['corner_marker'];
|
||||||
|
_firstPromotionPrice = json['first_promotion_price'];
|
||||||
|
_version = json['version'];
|
||||||
|
_userLevel = json['user_level'];
|
||||||
|
_vipDays = json['vip_days'];
|
||||||
|
_userPromiseLevel = json['user_promise_level'];
|
||||||
|
_useOnce = json['use_once'];
|
||||||
|
_sendCoinTtl = json['send_coin_ttl'];
|
||||||
|
_backhaulPercent = json['backhaul_percent'];
|
||||||
|
_payTemplateId = json['pay_template_id'];
|
||||||
|
_deletedAt = json['deleted_at'];
|
||||||
|
_payProductId = json['pay_product_id'];
|
||||||
|
_extInfo = json['ext_info'] != null ? ExtInfo.fromJson(json['ext_info']) : null;
|
||||||
|
_factor = json['factor'];
|
||||||
|
_shortType = json['short_type'];
|
||||||
|
_vipTypeKey = json['vip_type_key'];
|
||||||
|
_autoSub = json['auto_sub'];
|
||||||
|
_size = json['size'];
|
||||||
|
_orderCode = json['orderCode'];
|
||||||
|
_transactionId = json['transactionId'];
|
||||||
|
_serverVerificationData = json['serverVerificationData'];
|
||||||
|
_productDetails = json['productDetails'];
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _id;
|
||||||
|
String? _status;
|
||||||
|
String? _price;
|
||||||
|
int? _coins;
|
||||||
|
int? _sendCoins;
|
||||||
|
String? _buyType;
|
||||||
|
int? _sort;
|
||||||
|
String? _description;
|
||||||
|
String? _vipType;
|
||||||
|
String? _title;
|
||||||
|
String? _brief;
|
||||||
|
String? _createdAt;
|
||||||
|
String? _originPrice;
|
||||||
|
String? _backhaulPrice;
|
||||||
|
String? _iosTemplateId;
|
||||||
|
String? _androidTemplateId;
|
||||||
|
String? _currency;
|
||||||
|
String? _updatedAt;
|
||||||
|
String? _translateKey;
|
||||||
|
String? _platform;
|
||||||
|
int? _langId;
|
||||||
|
String? _cornerMarker;
|
||||||
|
String? _firstPromotionPrice;
|
||||||
|
int? _version;
|
||||||
|
String? _userLevel;
|
||||||
|
int? _vipDays;
|
||||||
|
String? _userPromiseLevel;
|
||||||
|
int? _useOnce;
|
||||||
|
int? _sendCoinTtl;
|
||||||
|
int? _backhaulPercent;
|
||||||
|
int? _payTemplateId;
|
||||||
|
String? _deletedAt;
|
||||||
|
int? _payProductId;
|
||||||
|
ExtInfo? _extInfo;
|
||||||
|
String? _factor;
|
||||||
|
String? _shortType;
|
||||||
|
String? _vipTypeKey;
|
||||||
|
String? _autoSub;
|
||||||
|
String? _size;
|
||||||
|
String? _orderCode;
|
||||||
|
String? _transactionId;
|
||||||
|
String? _serverVerificationData;
|
||||||
|
ProductDetails? _productDetails;
|
||||||
|
|
||||||
|
KtGoodsBean copyWith({
|
||||||
|
int? id,
|
||||||
|
String? status,
|
||||||
|
String? price,
|
||||||
|
int? coins,
|
||||||
|
int? sendCoins,
|
||||||
|
String? buyType,
|
||||||
|
int? sort,
|
||||||
|
String? description,
|
||||||
|
String? vipType,
|
||||||
|
String? title,
|
||||||
|
String? brief,
|
||||||
|
String? createdAt,
|
||||||
|
String? originPrice,
|
||||||
|
String? backhaulPrice,
|
||||||
|
String? iosTemplateId,
|
||||||
|
String? androidTemplateId,
|
||||||
|
String? currency,
|
||||||
|
String? updatedAt,
|
||||||
|
String? translateKey,
|
||||||
|
String? platform,
|
||||||
|
int? langId,
|
||||||
|
String? cornerMarker,
|
||||||
|
String? firstPromotionPrice,
|
||||||
|
int? version,
|
||||||
|
String? userLevel,
|
||||||
|
int? vipDays,
|
||||||
|
String? userPromiseLevel,
|
||||||
|
int? useOnce,
|
||||||
|
int? sendCoinTtl,
|
||||||
|
int? backhaulPercent,
|
||||||
|
int? payTemplateId,
|
||||||
|
String? deletedAt,
|
||||||
|
int? payProductId,
|
||||||
|
ExtInfo? extInfo,
|
||||||
|
String? factor,
|
||||||
|
String? shortType,
|
||||||
|
String? vipTypeKey,
|
||||||
|
String? autoSub,
|
||||||
|
String? size,
|
||||||
|
String? orderCode,
|
||||||
|
String? transactionId,
|
||||||
|
String? serverVerificationData,
|
||||||
|
ProductDetails? productDetails,
|
||||||
|
}) => KtGoodsBean(
|
||||||
|
id: id ?? _id,
|
||||||
|
status: status ?? _status,
|
||||||
|
price: price ?? _price,
|
||||||
|
coins: coins ?? _coins,
|
||||||
|
sendCoins: sendCoins ?? _sendCoins,
|
||||||
|
buyType: buyType ?? _buyType,
|
||||||
|
sort: sort ?? _sort,
|
||||||
|
description: description ?? _description,
|
||||||
|
vipType: vipType ?? _vipType,
|
||||||
|
title: title ?? _title,
|
||||||
|
brief: brief ?? _brief,
|
||||||
|
createdAt: createdAt ?? _createdAt,
|
||||||
|
originPrice: originPrice ?? _originPrice,
|
||||||
|
backhaulPrice: backhaulPrice ?? _backhaulPrice,
|
||||||
|
iosTemplateId: iosTemplateId ?? _iosTemplateId,
|
||||||
|
androidTemplateId: androidTemplateId ?? _androidTemplateId,
|
||||||
|
currency: currency ?? _currency,
|
||||||
|
updatedAt: updatedAt ?? _updatedAt,
|
||||||
|
translateKey: translateKey ?? _translateKey,
|
||||||
|
platform: platform ?? _platform,
|
||||||
|
langId: langId ?? _langId,
|
||||||
|
cornerMarker: cornerMarker ?? _cornerMarker,
|
||||||
|
firstPromotionPrice: firstPromotionPrice ?? _firstPromotionPrice,
|
||||||
|
version: version ?? _version,
|
||||||
|
userLevel: userLevel ?? _userLevel,
|
||||||
|
vipDays: vipDays ?? _vipDays,
|
||||||
|
userPromiseLevel: userPromiseLevel ?? _userPromiseLevel,
|
||||||
|
useOnce: useOnce ?? _useOnce,
|
||||||
|
sendCoinTtl: sendCoinTtl ?? _sendCoinTtl,
|
||||||
|
backhaulPercent: backhaulPercent ?? _backhaulPercent,
|
||||||
|
payTemplateId: payTemplateId ?? _payTemplateId,
|
||||||
|
deletedAt: deletedAt ?? _deletedAt,
|
||||||
|
payProductId: payProductId ?? _payProductId,
|
||||||
|
extInfo: extInfo ?? _extInfo,
|
||||||
|
factor: factor ?? _factor,
|
||||||
|
shortType: shortType ?? _shortType,
|
||||||
|
vipTypeKey: vipTypeKey ?? _vipTypeKey,
|
||||||
|
autoSub: autoSub ?? _autoSub,
|
||||||
|
size: size ?? _size,
|
||||||
|
orderCode: orderCode ?? _orderCode,
|
||||||
|
transactionId: transactionId ?? _transactionId,
|
||||||
|
serverVerificationData: serverVerificationData ?? _serverVerificationData,
|
||||||
|
productDetails: productDetails ?? _productDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
int? get id => _id;
|
||||||
|
|
||||||
|
String? get status => _status;
|
||||||
|
|
||||||
|
String? get price => _price;
|
||||||
|
|
||||||
|
int? get coins => _coins;
|
||||||
|
|
||||||
|
int? get sendCoins => _sendCoins;
|
||||||
|
|
||||||
|
String? get buyType => _buyType;
|
||||||
|
|
||||||
|
int? get sort => _sort;
|
||||||
|
|
||||||
|
String? get description => _description;
|
||||||
|
|
||||||
|
String? get vipType => _vipType;
|
||||||
|
|
||||||
|
String? get title => _title;
|
||||||
|
|
||||||
|
String? get brief => _brief;
|
||||||
|
|
||||||
|
String? get createdAt => _createdAt;
|
||||||
|
|
||||||
|
String? get originPrice => _originPrice;
|
||||||
|
|
||||||
|
String? get backhaulPrice => _backhaulPrice;
|
||||||
|
|
||||||
|
String? get iosTemplateId => _iosTemplateId;
|
||||||
|
|
||||||
|
String? get androidTemplateId => _androidTemplateId;
|
||||||
|
|
||||||
|
String? get currency => _currency;
|
||||||
|
|
||||||
|
String? get updatedAt => _updatedAt;
|
||||||
|
|
||||||
|
String? get translateKey => _translateKey;
|
||||||
|
|
||||||
|
String? get platform => _platform;
|
||||||
|
|
||||||
|
int? get langId => _langId;
|
||||||
|
|
||||||
|
String? get cornerMarker => _cornerMarker;
|
||||||
|
|
||||||
|
String? get firstPromotionPrice => _firstPromotionPrice;
|
||||||
|
|
||||||
|
int? get version => _version;
|
||||||
|
|
||||||
|
String? get userLevel => _userLevel;
|
||||||
|
|
||||||
|
int? get vipDays => _vipDays;
|
||||||
|
|
||||||
|
String? get userPromiseLevel => _userPromiseLevel;
|
||||||
|
|
||||||
|
int? get useOnce => _useOnce;
|
||||||
|
|
||||||
|
int? get sendCoinTtl => _sendCoinTtl;
|
||||||
|
|
||||||
|
int? get backhaulPercent => _backhaulPercent;
|
||||||
|
|
||||||
|
int? get payTemplateId => _payTemplateId;
|
||||||
|
|
||||||
|
String? get deletedAt => _deletedAt;
|
||||||
|
|
||||||
|
int? get payProductId => _payProductId;
|
||||||
|
|
||||||
|
ExtInfo? get extInfo => _extInfo;
|
||||||
|
|
||||||
|
String? get factor => _factor;
|
||||||
|
|
||||||
|
String? get shortType => _shortType;
|
||||||
|
|
||||||
|
String? get vipTypeKey => _vipTypeKey;
|
||||||
|
|
||||||
|
String? get autoSub => _autoSub;
|
||||||
|
|
||||||
|
String? get size => _size;
|
||||||
|
|
||||||
|
String? get orderCode => _orderCode;
|
||||||
|
|
||||||
|
String? get transactionId => _transactionId;
|
||||||
|
|
||||||
|
String? get serverVerificationData => _serverVerificationData;
|
||||||
|
|
||||||
|
ProductDetails? get productDetails => _productDetails;
|
||||||
|
|
||||||
|
// set方法补充
|
||||||
|
set id(int? value) => _id = value;
|
||||||
|
|
||||||
|
set status(String? value) => _status = value;
|
||||||
|
|
||||||
|
set price(String? value) => _price = value;
|
||||||
|
|
||||||
|
set coins(int? value) => _coins = value;
|
||||||
|
|
||||||
|
set sendCoins(int? value) => _sendCoins = value;
|
||||||
|
|
||||||
|
set buyType(String? value) => _buyType = value;
|
||||||
|
|
||||||
|
set sort(int? value) => _sort = value;
|
||||||
|
|
||||||
|
set description(String? value) => _description = value;
|
||||||
|
|
||||||
|
set vipType(String? value) => _vipType = value;
|
||||||
|
|
||||||
|
set title(String? value) => _title = value;
|
||||||
|
|
||||||
|
set brief(String? value) => _brief = value;
|
||||||
|
|
||||||
|
set createdAt(String? value) => _createdAt = value;
|
||||||
|
|
||||||
|
set originPrice(String? value) => _originPrice = value;
|
||||||
|
|
||||||
|
set backhaulPrice(String? value) => _backhaulPrice = value;
|
||||||
|
|
||||||
|
set iosTemplateId(String? value) => _iosTemplateId = value;
|
||||||
|
|
||||||
|
set androidTemplateId(String? value) => _androidTemplateId = value;
|
||||||
|
|
||||||
|
set currency(String? value) => _currency = value;
|
||||||
|
|
||||||
|
set updatedAt(String? value) => _updatedAt = value;
|
||||||
|
|
||||||
|
set translateKey(String? value) => _translateKey = value;
|
||||||
|
|
||||||
|
set platform(String? value) => _platform = value;
|
||||||
|
|
||||||
|
set langId(int? value) => _langId = value;
|
||||||
|
|
||||||
|
set cornerMarker(String? value) => _cornerMarker = value;
|
||||||
|
|
||||||
|
set firstPromotionPrice(String? value) => _firstPromotionPrice = value;
|
||||||
|
|
||||||
|
set version(int? value) => _version = value;
|
||||||
|
|
||||||
|
set userLevel(String? value) => _userLevel = value;
|
||||||
|
|
||||||
|
set vipDays(int? value) => _vipDays = value;
|
||||||
|
|
||||||
|
set userPromiseLevel(String? value) => _userPromiseLevel = value;
|
||||||
|
|
||||||
|
set useOnce(int? value) => _useOnce = value;
|
||||||
|
|
||||||
|
set sendCoinTtl(int? value) => _sendCoinTtl = value;
|
||||||
|
|
||||||
|
set backhaulPercent(int? value) => _backhaulPercent = value;
|
||||||
|
|
||||||
|
set payTemplateId(int? value) => _payTemplateId = value;
|
||||||
|
|
||||||
|
set deletedAt(String? value) => _deletedAt = value;
|
||||||
|
|
||||||
|
set payProductId(int? value) => _payProductId = value;
|
||||||
|
|
||||||
|
set extInfo(ExtInfo? value) => _extInfo = value;
|
||||||
|
|
||||||
|
set factor(String? value) => _factor = value;
|
||||||
|
|
||||||
|
set shortType(String? value) => _shortType = value;
|
||||||
|
|
||||||
|
set vipTypeKey(String? value) => _vipTypeKey = value;
|
||||||
|
|
||||||
|
set autoSub(String? value) => _autoSub = value;
|
||||||
|
|
||||||
|
set size(String? value) => _size = value;
|
||||||
|
|
||||||
|
set orderCode(String? value) => _orderCode = value;
|
||||||
|
|
||||||
|
set transactionId(String? value) => _transactionId = value;
|
||||||
|
|
||||||
|
set serverVerificationData(String? value) => _serverVerificationData = value;
|
||||||
|
|
||||||
|
set productDetails(ProductDetails? value) => _productDetails = value;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final map = <String, dynamic>{};
|
||||||
|
map['id'] = _id;
|
||||||
|
map['status'] = _status;
|
||||||
|
map['price'] = _price;
|
||||||
|
map['coins'] = _coins;
|
||||||
|
map['send_coins'] = _sendCoins;
|
||||||
|
map['buy_type'] = _buyType;
|
||||||
|
map['sort'] = _sort;
|
||||||
|
map['description'] = _description;
|
||||||
|
map['vip_type'] = _vipType;
|
||||||
|
map['title'] = _title;
|
||||||
|
map['brief'] = _brief;
|
||||||
|
map['created_at'] = _createdAt;
|
||||||
|
map['origin_price'] = _originPrice;
|
||||||
|
map['backhaul_price'] = _backhaulPrice;
|
||||||
|
map['ios_template_id'] = _iosTemplateId;
|
||||||
|
map['android_template_id'] = _androidTemplateId;
|
||||||
|
map['currency'] = _currency;
|
||||||
|
map['updated_at'] = _updatedAt;
|
||||||
|
map['translate_key'] = _translateKey;
|
||||||
|
map['platform'] = _platform;
|
||||||
|
map['lang_id'] = _langId;
|
||||||
|
map['corner_marker'] = _cornerMarker;
|
||||||
|
map['first_promotion_price'] = _firstPromotionPrice;
|
||||||
|
map['version'] = _version;
|
||||||
|
map['user_level'] = _userLevel;
|
||||||
|
map['vip_days'] = _vipDays;
|
||||||
|
map['user_promise_level'] = _userPromiseLevel;
|
||||||
|
map['use_once'] = _useOnce;
|
||||||
|
map['send_coin_ttl'] = _sendCoinTtl;
|
||||||
|
map['backhaul_percent'] = _backhaulPercent;
|
||||||
|
map['pay_template_id'] = _payTemplateId;
|
||||||
|
map['deleted_at'] = _deletedAt;
|
||||||
|
map['pay_product_id'] = _payProductId;
|
||||||
|
if (_extInfo != null) {
|
||||||
|
map['ext_info'] = _extInfo?.toJson();
|
||||||
|
}
|
||||||
|
map['factor'] = _factor;
|
||||||
|
map['short_type'] = _shortType;
|
||||||
|
map['vip_type_key'] = _vipTypeKey;
|
||||||
|
map['auto_sub'] = _autoSub;
|
||||||
|
map['size'] = _size;
|
||||||
|
map['serverVerificationData'] = _serverVerificationData;
|
||||||
|
map['transactionId'] = _transactionId;
|
||||||
|
map['orderCode'] = _orderCode;
|
||||||
|
// map['productDetails'] = _productDetails;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// size : "small"
|
||||||
|
/// extra_coins : 0
|
||||||
|
/// max_total_coins : 0
|
||||||
|
/// max_total_coins_pop : 0
|
||||||
|
/// is_vip_show : false
|
||||||
|
/// receive_coins_rate : 85
|
||||||
|
/// extra_day_coins : 85
|
||||||
|
/// sub_coins_txt_list : ["",""]
|
||||||
|
/// backhaul_percent_conf : [{"episode_num":50,"backhaul_percent":50},{"episode_num":80,"backhaul_percent":80}]
|
||||||
|
|
||||||
|
ExtInfo extInfoFromJson(String str) => ExtInfo.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String extInfoToJson(ExtInfo data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
class ExtInfo {
|
||||||
|
ExtInfo({
|
||||||
|
String? size,
|
||||||
|
int? maxTotalCoins,
|
||||||
|
int? maxTotalCoinsPop,
|
||||||
|
int? extraCoins,
|
||||||
|
bool? isVipShow = false,
|
||||||
|
String? receiveCoinsRate,
|
||||||
|
int? extraDayCoins,
|
||||||
|
bool? coinsWinBackShow,
|
||||||
|
List<String>? subCoinsTxtList,
|
||||||
|
List<BackhaulPercentConf>? backhaulPercentConf,
|
||||||
|
}) {
|
||||||
|
_size = size;
|
||||||
|
_maxTotalCoins = maxTotalCoins;
|
||||||
|
_maxTotalCoinsPop = maxTotalCoinsPop;
|
||||||
|
_extraCoins = extraCoins;
|
||||||
|
_isVipShow = isVipShow;
|
||||||
|
_receiveCoinsRate = receiveCoinsRate;
|
||||||
|
_extraDayCoins = extraDayCoins;
|
||||||
|
_coinsWinBackShow = coinsWinBackShow;
|
||||||
|
_subCoinsTxtList = subCoinsTxtList;
|
||||||
|
_backhaulPercentConf = backhaulPercentConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtInfo.fromJson(dynamic json) {
|
||||||
|
_size = json['size'];
|
||||||
|
_maxTotalCoins = json['max_total_coins'];
|
||||||
|
_maxTotalCoinsPop = json['max_total_coins_pop'];
|
||||||
|
_isVipShow = json['is_vip_show'];
|
||||||
|
_receiveCoinsRate = json['receive_coins_rate'];
|
||||||
|
_extraDayCoins = json['extra_day_coins'];
|
||||||
|
_extraCoins = json['extra_coins'];
|
||||||
|
_coinsWinBackShow = json['coins_win_back_show'];
|
||||||
|
_subCoinsTxtList = json['sub_coins_txt_list'] != null ? json['sub_coins_txt_list'].cast<String>() : [];
|
||||||
|
if (json['backhaul_percent_conf'] != null) {
|
||||||
|
_backhaulPercentConf = [];
|
||||||
|
json['backhaul_percent_conf'].forEach((v) {
|
||||||
|
_backhaulPercentConf?.add(BackhaulPercentConf.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _size;
|
||||||
|
int? _extraCoins;
|
||||||
|
int? _maxTotalCoins;
|
||||||
|
int? _maxTotalCoinsPop;
|
||||||
|
String? _receiveCoinsRate;
|
||||||
|
int? _extraDayCoins;
|
||||||
|
bool? _isVipShow;
|
||||||
|
bool? _coinsWinBackShow;
|
||||||
|
List<String>? _subCoinsTxtList;
|
||||||
|
List<BackhaulPercentConf>? _backhaulPercentConf;
|
||||||
|
|
||||||
|
ExtInfo copyWith({
|
||||||
|
String? size,
|
||||||
|
int? extraCoins,
|
||||||
|
int? maxTotalCoins,
|
||||||
|
int? maxTotalCoinsPop,
|
||||||
|
String? receiveCoinsRate,
|
||||||
|
int? extraDayCoins,
|
||||||
|
bool? coinsWinBackShow,
|
||||||
|
bool? isVipShow,
|
||||||
|
List<String>? subCoinsTxtList,
|
||||||
|
List<BackhaulPercentConf>? backhaulPercentConf,
|
||||||
|
}) => ExtInfo(
|
||||||
|
size: size ?? _size,
|
||||||
|
maxTotalCoins: maxTotalCoins ?? _maxTotalCoins,
|
||||||
|
maxTotalCoinsPop: maxTotalCoinsPop ?? _maxTotalCoinsPop,
|
||||||
|
receiveCoinsRate: receiveCoinsRate ?? _receiveCoinsRate,
|
||||||
|
extraDayCoins: extraDayCoins ?? _extraDayCoins,
|
||||||
|
isVipShow: isVipShow ?? _isVipShow,
|
||||||
|
extraCoins: extraCoins ?? _extraCoins,
|
||||||
|
coinsWinBackShow: coinsWinBackShow ?? _coinsWinBackShow,
|
||||||
|
subCoinsTxtList: subCoinsTxtList ?? _subCoinsTxtList,
|
||||||
|
backhaulPercentConf: backhaulPercentConf ?? _backhaulPercentConf,
|
||||||
|
);
|
||||||
|
|
||||||
|
String? get size => _size;
|
||||||
|
|
||||||
|
int? get extraCoins => _extraCoins;
|
||||||
|
|
||||||
|
int? get maxTotalCoins => _maxTotalCoins;
|
||||||
|
|
||||||
|
int? get maxTotalCoinsPop => _maxTotalCoinsPop;
|
||||||
|
|
||||||
|
String? get receiveCoinsRate => _receiveCoinsRate;
|
||||||
|
|
||||||
|
int? get extraDayCoins => _extraDayCoins;
|
||||||
|
|
||||||
|
bool? get coinsWinBackShow => _coinsWinBackShow;
|
||||||
|
|
||||||
|
bool? get isVipShow => _isVipShow;
|
||||||
|
|
||||||
|
List<String>? get subCoinsTxtList => _subCoinsTxtList;
|
||||||
|
|
||||||
|
List<BackhaulPercentConf>? get backhaulPercentConf => _backhaulPercentConf;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final map = <String, dynamic>{};
|
||||||
|
map['size'] = _size;
|
||||||
|
map['max_total_coins'] = _maxTotalCoins;
|
||||||
|
map['max_total_coins_pop'] = _maxTotalCoinsPop;
|
||||||
|
map['receive_coins_rate'] = _receiveCoinsRate;
|
||||||
|
map['extra_day_coins'] = _extraDayCoins;
|
||||||
|
map['is_vip_show'] = _isVipShow;
|
||||||
|
map['extra_coins'] = _extraCoins;
|
||||||
|
map['coins_win_back_show'] = _coinsWinBackShow;
|
||||||
|
map['sub_coins_txt_list'] = _subCoinsTxtList;
|
||||||
|
if (_backhaulPercentConf != null) {
|
||||||
|
map['backhaul_percent_conf'] = _backhaulPercentConf?.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// episode_num : 50
|
||||||
|
/// backhaul_percent : 50
|
||||||
|
|
||||||
|
BackhaulPercentConf backhaulPercentConfFromJson(String str) => BackhaulPercentConf.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String backhaulPercentConfToJson(BackhaulPercentConf data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
class BackhaulPercentConf {
|
||||||
|
BackhaulPercentConf({int? episodeNum, int? backhaulPercent}) {
|
||||||
|
_episodeNum = episodeNum;
|
||||||
|
_backhaulPercent = backhaulPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackhaulPercentConf.fromJson(dynamic json) {
|
||||||
|
_episodeNum = json['episode_num'];
|
||||||
|
_backhaulPercent = json['backhaul_percent'];
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _episodeNum;
|
||||||
|
int? _backhaulPercent;
|
||||||
|
|
||||||
|
BackhaulPercentConf copyWith({int? episodeNum, int? backhaulPercent}) =>
|
||||||
|
BackhaulPercentConf(episodeNum: episodeNum ?? _episodeNum, backhaulPercent: backhaulPercent ?? _backhaulPercent);
|
||||||
|
|
||||||
|
int? get episodeNum => _episodeNum;
|
||||||
|
|
||||||
|
int? get backhaulPercent => _backhaulPercent;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final map = <String, dynamic>{};
|
||||||
|
map['episode_num'] = _episodeNum;
|
||||||
|
map['backhaul_percent'] = _backhaulPercent;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
38
lib/kt_model/kt_order_bean.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
/// order_code : "O202507111000016870c46c4c3fd"
|
||||||
|
/// money : "9.99"
|
||||||
|
/// is_backhaul : 2
|
||||||
|
|
||||||
|
KtOrderBean ktOrderBeanFromJson(String str) => KtOrderBean.fromJson(json.decode(str));
|
||||||
|
String ktOrderBeanToJson(KtOrderBean data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
class KtOrderBean {
|
||||||
|
KtOrderBean({String? orderCode, String? money, int? isBackhaul}) {
|
||||||
|
_orderCode = orderCode;
|
||||||
|
_money = money;
|
||||||
|
_isBackhaul = isBackhaul;
|
||||||
|
}
|
||||||
|
|
||||||
|
KtOrderBean.fromJson(dynamic json) {
|
||||||
|
_orderCode = json['order_code'];
|
||||||
|
_money = json['money'];
|
||||||
|
_isBackhaul = json['is_backhaul'];
|
||||||
|
}
|
||||||
|
String? _orderCode;
|
||||||
|
String? _money;
|
||||||
|
int? _isBackhaul;
|
||||||
|
KtOrderBean copyWith({String? orderCode, String? money, int? isBackhaul}) =>
|
||||||
|
KtOrderBean(orderCode: orderCode ?? _orderCode, money: money ?? _money, isBackhaul: isBackhaul ?? _isBackhaul);
|
||||||
|
String? get orderCode => _orderCode;
|
||||||
|
String? get money => _money;
|
||||||
|
int? get isBackhaul => _isBackhaul;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final map = <String, dynamic>{};
|
||||||
|
map['order_code'] = _orderCode;
|
||||||
|
map['money'] = _money;
|
||||||
|
map['is_backhaul'] = _isBackhaul;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
159
lib/kt_model/kt_store_bean.dart
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'kt_goods_bean.dart';
|
||||||
|
|
||||||
|
/// sort : ["list_coins","list_sub_vip"]
|
||||||
|
/// list_vip : [{"id":93,"status":"enable","price":"59.99","coins":6000,"send_coins":6000,"buy_type":"coins","sort":12,"description":"","vip_type":"","title":"59.99 for coins","brief":"","created_at":"2025-04-15 07:01:47","origin_price":"0.00","backhaul_price":"59.99","ios_template_id":"coins_59.99","android_template_id":"coins_59.99","currency":"US$","updated_at":"2025-07-02 06:48:47","translate_key":"","platform":"all","lang_id":0,"corner_marker":"Popularity","first_promotion_price":"0.00","version":2,"user_level":"high","vip_days":0,"user_promise_level":"","use_once":0,"send_coin_ttl":1,"backhaul_percent":100,"pay_template_id":2,"deleted_at":"","pay_product_id":14,"ext_info":"{\"size\": \"big\", \"backhaul_percent_conf\": []}","factor":"1.00","short_type":"","vip_type_key":"","auto_sub":"Automatic renewal, cancel at any time","size":"big"}]
|
||||||
|
/// list_coins : [{"id":93,"status":"enable","price":"59.99","coins":6000,"send_coins":6000,"buy_type":"coins","sort":12,"description":"","vip_type":"","title":"59.99 for coins","brief":"","created_at":"2025-04-15 07:01:47","origin_price":"0.00","backhaul_price":"59.99","ios_template_id":"coins_59.99","android_template_id":"coins_59.99","currency":"US$","updated_at":"2025-07-02 06:48:47","translate_key":"","platform":"all","lang_id":0,"corner_marker":"Popularity","first_promotion_price":"0.00","version":2,"user_level":"high","vip_days":0,"user_promise_level":"","use_once":0,"send_coin_ttl":1,"backhaul_percent":100,"pay_template_id":2,"deleted_at":"","pay_product_id":14,"ext_info":"{\"size\": \"big\", \"backhaul_percent_conf\": []}","factor":"1.00","short_type":"","vip_type_key":"","auto_sub":"Automatic renewal, cancel at any time","size":"big"}]
|
||||||
|
/// list_sub_vip : [{"id":104,"status":"enable","price":"49.99","coins":0,"send_coins":4500,"buy_type":"sub_vip","sort":5,"description":"Unlimited access to all series for 1 month (Ad-free)","vip_type":"month","title":"Monthly membership","brief":"Monthly membership","created_at":"2025-04-15 07:30:47","origin_price":"0.00","backhaul_price":"24.99","ios_template_id":"sub.m_49.99","android_template_id":"sub.m_49.99","currency":"US$","updated_at":"2025-06-20 07:27:23","translate_key":"","platform":"all","lang_id":0,"corner_marker":"Popularity","first_promotion_price":"0.00","version":2,"user_level":"high","vip_days":0,"user_promise_level":"null","use_once":0,"send_coin_ttl":35,"backhaul_percent":100,"pay_template_id":2,"deleted_at":"null","pay_product_id":25,"ext_info":"{\"size\": \"\", \"backhaul_percent_conf\": []}","factor":"1.00","short_type":"Monthly","vip_type_key":"month","auto_sub":"Automatic renewal, cancel at any time","size":"small"}]
|
||||||
|
/// list_retrieve : [{"id":93,"status":"enable","price":"59.99","coins":6000,"send_coins":6000,"buy_type":"coins","sort":12,"description":"","vip_type":"","title":"59.99 for coins","brief":"","created_at":"2025-04-15 07:01:47","origin_price":"0.00","backhaul_price":"59.99","ios_template_id":"coins_59.99","android_template_id":"coins_59.99","currency":"US$","updated_at":"2025-07-02 06:48:47","translate_key":"","platform":"all","lang_id":0,"corner_marker":"Popularity","first_promotion_price":"0.00","version":2,"user_level":"high","vip_days":0,"user_promise_level":"","use_once":0,"send_coin_ttl":1,"backhaul_percent":100,"pay_template_id":2,"deleted_at":"","pay_product_id":14,"ext_info":"{\"size\": \"big\", \"backhaul_percent_conf\": []}","factor":"1.00","short_type":"","vip_type_key":"","auto_sub":"Automatic renewal, cancel at any time","size":"big"}]
|
||||||
|
|
||||||
|
KtStoreBean ktStoreBeanFromJson(String str) => KtStoreBean.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String ktStoreBeanToJson(KtStoreBean data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
class KtStoreBean {
|
||||||
|
KtStoreBean({
|
||||||
|
int? showType,
|
||||||
|
int? payMode,
|
||||||
|
List<String>? sort,
|
||||||
|
List<KtGoodsBean>? listVip,
|
||||||
|
List<KtGoodsBean>? listCoins,
|
||||||
|
List<KtGoodsBean>? listSubCoins,
|
||||||
|
List<KtGoodsBean>? listSubVip,
|
||||||
|
List<KtGoodsBean>? listRetrieve,
|
||||||
|
}) {
|
||||||
|
_sort = sort;
|
||||||
|
_payMode = payMode;
|
||||||
|
_showType = showType;
|
||||||
|
_listVip = listVip;
|
||||||
|
_listCoins = listCoins;
|
||||||
|
_listSubCoins = listSubCoins;
|
||||||
|
_listSubVip = listSubVip;
|
||||||
|
_listRetrieve = listRetrieve;
|
||||||
|
}
|
||||||
|
|
||||||
|
KtStoreBean.fromJson(dynamic json) {
|
||||||
|
_showType = json['show_type'];
|
||||||
|
_payMode = json['pay_mode'];
|
||||||
|
_sort = json['sort'] != null ? json['sort'].cast<String>() : [];
|
||||||
|
if (json['list_vip'] != null) {
|
||||||
|
_listVip = [];
|
||||||
|
json['list_vip'].forEach((v) {
|
||||||
|
_listVip?.add(KtGoodsBean.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (json['list_coins'] != null) {
|
||||||
|
_listCoins = [];
|
||||||
|
json['list_coins'].forEach((v) {
|
||||||
|
_listCoins?.add(KtGoodsBean.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (json['list_sub_coins'] != null) {
|
||||||
|
_listSubCoins = [];
|
||||||
|
json['list_sub_coins'].forEach((v) {
|
||||||
|
_listSubCoins?.add(KtGoodsBean.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (json['list_sub_vip'] != null) {
|
||||||
|
_listSubVip = [];
|
||||||
|
json['list_sub_vip'].forEach((v) {
|
||||||
|
_listSubVip?.add(KtGoodsBean.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (json['list_retrieve'] != null) {
|
||||||
|
_listRetrieve = [];
|
||||||
|
json['list_retrieve'].forEach((v) {
|
||||||
|
_listRetrieve?.add(KtGoodsBean.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int? _payMode;
|
||||||
|
int? _showType;
|
||||||
|
List<String>? _sort;
|
||||||
|
List<KtGoodsBean>? _listVip;
|
||||||
|
List<KtGoodsBean>? _listCoins;
|
||||||
|
List<KtGoodsBean>? _listSubCoins;
|
||||||
|
List<KtGoodsBean>? _listSubVip;
|
||||||
|
List<KtGoodsBean>? _listRetrieve;
|
||||||
|
|
||||||
|
KtStoreBean copyWith({
|
||||||
|
int? payMode,
|
||||||
|
int? showType,
|
||||||
|
List<String>? sort,
|
||||||
|
List<KtGoodsBean>? listVip,
|
||||||
|
List<KtGoodsBean>? listCoins,
|
||||||
|
List<KtGoodsBean>? listSubCoins,
|
||||||
|
List<KtGoodsBean>? listSubVip,
|
||||||
|
List<KtGoodsBean>? listRetrieve,
|
||||||
|
}) => KtStoreBean(
|
||||||
|
payMode: payMode ?? _payMode,
|
||||||
|
showType: showType ?? _showType,
|
||||||
|
sort: sort ?? _sort,
|
||||||
|
listVip: listVip ?? _listVip,
|
||||||
|
listCoins: listCoins ?? _listCoins,
|
||||||
|
listSubVip: listSubVip ?? _listSubVip,
|
||||||
|
listSubCoins: listSubCoins ?? _listSubCoins,
|
||||||
|
listRetrieve: listRetrieve ?? _listRetrieve,
|
||||||
|
);
|
||||||
|
|
||||||
|
int? get payMode => _payMode;
|
||||||
|
|
||||||
|
int? get showType => _showType;
|
||||||
|
|
||||||
|
List<String>? get sort => _sort;
|
||||||
|
|
||||||
|
List<KtGoodsBean>? get listVip => _listVip;
|
||||||
|
|
||||||
|
List<KtGoodsBean>? get listCoins => _listCoins;
|
||||||
|
|
||||||
|
List<KtGoodsBean>? get listSubCoins => _listSubCoins;
|
||||||
|
|
||||||
|
List<KtGoodsBean>? get listSubVip => _listSubVip;
|
||||||
|
|
||||||
|
List<KtGoodsBean>? get listRetrieve => _listRetrieve;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final map = <String, dynamic>{};
|
||||||
|
map['show_type'] = _showType;
|
||||||
|
map['pay_mode'] = _payMode;
|
||||||
|
map['sort'] = _sort;
|
||||||
|
if (_listVip != null) {
|
||||||
|
map['list_vip'] = _listVip?.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
if (_listCoins != null) {
|
||||||
|
map['list_coins'] = _listCoins?.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
if (_listSubVip != null) {
|
||||||
|
map['list_sub_vip'] = _listSubVip?.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
if (_listSubCoins != null) {
|
||||||
|
map['list_sub_coins'] = _listSubCoins?.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
if (_listRetrieve != null) {
|
||||||
|
map['list_retrieve'] = _listRetrieve?.map((v) => v.toJson()).toList();
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KtGoodsBean listRetrieveFromJson(String str) => KtGoodsBean.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String listRetrieveToJson(KtGoodsBean data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
KtGoodsBean listSubVipFromJson(String str) => KtGoodsBean.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String listSubVipToJson(KtGoodsBean data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
KtGoodsBean listSubCoinsFromJson(String str) => KtGoodsBean.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String listSubCoinsToJson(KtGoodsBean data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
KtGoodsBean listCoinsFromJson(String str) => KtGoodsBean.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String listCoinsToJson(KtGoodsBean data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
KtGoodsBean listVipFromJson(String str) => KtGoodsBean.fromJson(json.decode(str));
|
||||||
|
|
||||||
|
String listVipToJson(KtGoodsBean data) => json.encode(data.toJson());
|
@ -21,6 +21,7 @@ class KtExploreLogic extends GetxController {
|
|||||||
final PageController pageController = PageController(viewportFraction: .95);
|
final PageController pageController = PageController(viewportFraction: .95);
|
||||||
final int preloadRange = 1;
|
final int preloadRange = 1;
|
||||||
bool isRefresh = false;
|
bool isRefresh = false;
|
||||||
|
final mainLogic = Get.find<KtMainLogic>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onReady() {
|
void onReady() {
|
||||||
@ -123,10 +124,10 @@ class KtExploreLogic extends GetxController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (state.controllers[index] != null) {
|
if (state.controllers[index] != null) {
|
||||||
state.controllers[index]?.play();
|
if (mainLogic.curIndex == 1) state.controllers[index]?.play();
|
||||||
} else {
|
} else {
|
||||||
if (!isToggle) await _initializeController(index);
|
if (!isToggle) await _initializeController(index);
|
||||||
state.controllers[index]?.play();
|
if (mainLogic.curIndex == 1) state.controllers[index]?.play();
|
||||||
}
|
}
|
||||||
// 预加载新的相邻视频,并释放多余控制器
|
// 预加载新的相邻视频,并释放多余控制器
|
||||||
_preloadAdjacentVideos();
|
_preloadAdjacentVideos();
|
||||||
@ -165,13 +166,16 @@ class KtExploreLogic extends GetxController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await controller.initialize();
|
await controller.initialize();
|
||||||
if (index == state.currentPage) {
|
if (index == state.currentPage && mainLogic.curIndex == 1) {
|
||||||
final mainLogic = Get.find<KtMainLogic>();
|
controller.play();
|
||||||
if(mainLogic.curIndex == 1) controller.play();
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
controller.addListener(() {
|
controller.addListener(() {
|
||||||
if (state.currentPage == index) update();
|
if (state.currentPage == index && mainLogic.curIndex == 1) {
|
||||||
|
update();
|
||||||
|
} else {
|
||||||
|
controller.pause();
|
||||||
|
}
|
||||||
|
|
||||||
if (controller.value.isCompleted && !controller.value.isBuffering) {
|
if (controller.value.isCompleted && !controller.value.isBuffering) {
|
||||||
onPageChanged(index + 1, isToggle: true);
|
onPageChanged(index + 1, isToggle: true);
|
||||||
|
@ -154,7 +154,7 @@ class SignInActivityPageState extends State<KtSearchPage> with RouteAware {
|
|||||||
allowedOriginRules: {"*"},
|
allowedOriginRules: {"*"},
|
||||||
onPostMessage:
|
onPostMessage:
|
||||||
(message, sourceOrigin, isMainFrame, replyProxy) {
|
(message, sourceOrigin, isMainFrame, replyProxy) {
|
||||||
print('----callback--jsonS:$message');
|
debugPrint('----callback--jsonS:$message');
|
||||||
if (message?.data != null) {
|
if (message?.data != null) {
|
||||||
_handleWebMessage(message?.data);
|
_handleWebMessage(message?.data);
|
||||||
}
|
}
|
||||||
@ -247,7 +247,7 @@ class SignInActivityPageState extends State<KtSearchPage> with RouteAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildWidget() {
|
Widget _buildWidget() {
|
||||||
print('----loadStatus:$loadingStatus');
|
debugPrint('----loadStatus:$loadingStatus');
|
||||||
switch (loadingStatus) {
|
switch (loadingStatus) {
|
||||||
case KtLoadStatusType.loading:
|
case KtLoadStatusType.loading:
|
||||||
return Center(child: CircularProgressIndicator());
|
return Center(child: CircularProgressIndicator());
|
||||||
|
@ -12,20 +12,21 @@ import 'package:get/get.dart';
|
|||||||
import '../../../dio_cilent/kt_apis.dart';
|
import '../../../dio_cilent/kt_apis.dart';
|
||||||
import '../../../kt_utils/kt_keys.dart';
|
import '../../../kt_utils/kt_keys.dart';
|
||||||
import '../../../kt_utils/kt_utils.dart';
|
import '../../../kt_utils/kt_utils.dart';
|
||||||
|
import '../../../kt_widgets/kt_status_widget.dart';
|
||||||
import '../../../main.dart';
|
import '../../../main.dart';
|
||||||
|
|
||||||
class WalletPage extends StatefulWidget {
|
class KtOrderRecordPage extends StatefulWidget {
|
||||||
const WalletPage({super.key});
|
const KtOrderRecordPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SignInActivityPageState createState() => SignInActivityPageState();
|
KtOrderRecordPageState createState() => KtOrderRecordPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
class KtOrderRecordPageState extends State<KtOrderRecordPage> with RouteAware {
|
||||||
InAppWebViewController? _webViewController;
|
InAppWebViewController? _webViewController;
|
||||||
late PullToRefreshController _webRefreshController;
|
late PullToRefreshController _webRefreshController;
|
||||||
late Map<String, String> _userData;
|
late Map<String, String> _userData;
|
||||||
// LoadStatusType loadingStatus = LoadStatusType.loading;
|
KtLoadStatusType loadingStatus = KtLoadStatusType.loading;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -39,10 +40,10 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
|||||||
'time_zone': KtUtils.getTimeZoneOffset(DateTime.now()),
|
'time_zone': KtUtils.getTimeZoneOffset(DateTime.now()),
|
||||||
'type': Platform.isAndroid ? 'android' : 'ios',
|
'type': Platform.isAndroid ? 'android' : 'ios',
|
||||||
'lang': 'en',
|
'lang': 'en',
|
||||||
'theme': 'theme_7',
|
// 'theme': 'theme_7',
|
||||||
'token': SpUtil.getString(KtKeys.token) ?? '',
|
'token': SpUtil.getString(KtKeys.token) ?? '',
|
||||||
};
|
};
|
||||||
print('-----userData:${_userData}');
|
debugPrint('-----userData:$_userData');
|
||||||
}
|
}
|
||||||
|
|
||||||
_initRefreshController() {
|
_initRefreshController() {
|
||||||
@ -108,32 +109,24 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Container(
|
||||||
extendBodyBehindAppBar: true,
|
width: ScreenUtil().screenWidth,
|
||||||
// appBar: AppBar(
|
height: ScreenUtil().screenHeight,
|
||||||
// leading: Container(
|
padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight + 20.w),
|
||||||
// padding: EdgeInsets.only(left: 15.w, bottom: 6.w),
|
decoration: BoxDecoration(
|
||||||
// child: IconButton(
|
image: DecorationImage(
|
||||||
// icon: Image.asset('ic_back.png'.ktIcon, width: 24.w),
|
image: AssetImage('bg1.png'.ktIcon),
|
||||||
// onPressed: () => Navigator.of(context).maybePop(),
|
fit: BoxFit.fill,
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
body: Container(
|
|
||||||
width: ScreenUtil().screenWidth,
|
|
||||||
height: ScreenUtil().screenHeight,
|
|
||||||
padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight + 20.w),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
image: DecorationImage(
|
|
||||||
image: AssetImage('bg1.png'.ktIcon),
|
|
||||||
fit: BoxFit.fill,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Container(
|
),
|
||||||
margin: EdgeInsets.only(top: 16.w),
|
child: Container(
|
||||||
child: Stack(
|
margin: EdgeInsets.only(top: 16.w),
|
||||||
children: [
|
child: Stack(
|
||||||
InAppWebView(
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.only(top: 20.w),
|
||||||
|
child: InAppWebView(
|
||||||
pullToRefreshController: _webRefreshController,
|
pullToRefreshController: _webRefreshController,
|
||||||
initialSettings: InAppWebViewSettings(
|
initialSettings: InAppWebViewSettings(
|
||||||
cacheEnabled: false,
|
cacheEnabled: false,
|
||||||
@ -167,37 +160,26 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
|||||||
handlerName: 'getUserInfo',
|
handlerName: 'getUserInfo',
|
||||||
callback: (_) => jsonEncode(_userData),
|
callback: (_) => jsonEncode(_userData),
|
||||||
);
|
);
|
||||||
if (Platform.isIOS) {
|
await _webViewController?.addWebMessageListener(
|
||||||
// ios
|
WebMessageListener(
|
||||||
await _webViewController?.addWebMessageListener(
|
jsObjectName: "goStore",
|
||||||
WebMessageListener(
|
allowedOriginRules: {"*"},
|
||||||
jsObjectName: "js2app",
|
onPostMessage:
|
||||||
allowedOriginRules: {"*"},
|
(message, sourceOrigin, isMainFrame, replyProxy) {
|
||||||
onPostMessage:
|
debugPrint('----callback--jsonS:$message');
|
||||||
(message, sourceOrigin, isMainFrame, replyProxy) {
|
if (message != null) {
|
||||||
if (message?.data != null) {
|
Get.toNamed(KtRoutes.store);
|
||||||
_handleWebMessage(message?.data);
|
}
|
||||||
}
|
},
|
||||||
},
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
} else if (Platform.isAndroid) {
|
|
||||||
_webViewController?.addJavaScriptHandler(
|
|
||||||
handlerName: "js2app",
|
|
||||||
callback: (jsonS) {
|
|
||||||
if (jsonS.isNotEmpty) {
|
|
||||||
_handleWebMessage(jsonS.toString());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await _webViewController?.loadUrl(
|
await _webViewController?.loadUrl(
|
||||||
urlRequest: URLRequest(url: WebUri(KtApis.WEB_SITE_WALLET)),
|
urlRequest: URLRequest(url: WebUri(KtApis.WEB_SITE_WALLET)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onLoadStart: (controller, url) {
|
onLoadStart: (controller, url) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// loadingStatus = LoadStatusType.loading;
|
loadingStatus = KtLoadStatusType.loading;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onLoadStop: (controller, url) async {
|
onLoadStop: (controller, url) async {
|
||||||
@ -213,25 +195,25 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
|||||||
controller.evaluateJavascript(
|
controller.evaluateJavascript(
|
||||||
source:
|
source:
|
||||||
'''
|
'''
|
||||||
if(typeof window.receiveDataFromNative === 'function') {
|
if(typeof window.receiveDataFromNative === 'function') {
|
||||||
window.receiveDataFromNative($userJsonStr);
|
window.receiveDataFromNative($userJsonStr);
|
||||||
}
|
}
|
||||||
''',
|
''',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else if (Platform.isAndroid) {
|
} else if (Platform.isAndroid) {
|
||||||
await controller.evaluateJavascript(
|
await controller.evaluateJavascript(
|
||||||
source: """
|
source: """
|
||||||
window.AndroidInterface = {
|
window.AndroidInterface = {
|
||||||
getUserInfo: async function () {
|
getUserInfo: async function () {
|
||||||
return window.flutter_inappwebview.callHandler('getUserInfo');
|
return window.flutter_inappwebview.callHandler('getUserInfo');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
""",
|
""",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
// loadingStatus = LoadStatusType.loadSuccess;
|
loadingStatus = KtLoadStatusType.loadSuccess;
|
||||||
});
|
});
|
||||||
_webRefreshController.endRefreshing();
|
_webRefreshController.endRefreshing();
|
||||||
},
|
},
|
||||||
@ -239,16 +221,68 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
|||||||
_webRefreshController.endRefreshing();
|
_webRefreshController.endRefreshing();
|
||||||
Future.delayed(const Duration(milliseconds: 100)).then((_) {
|
Future.delayed(const Duration(milliseconds: 100)).then((_) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// loadingStatus = LoadStatusType.loadFailed;
|
loadingStatus = KtLoadStatusType.loadFailed;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// _buildWidget(),
|
),
|
||||||
],
|
_buildWidget(),
|
||||||
),
|
Positioned(left: 0,right: 0, child: _buildAppBar()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildAppBar() {
|
||||||
|
return SizedBox(
|
||||||
|
width: ScreenUtil().screenWidth,
|
||||||
|
height: 40.w,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Image.asset('ic_back.png'.ktIcon, width: 8.w),
|
||||||
|
onPressed: () => Navigator.of(context).maybePop(),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'My Wallet',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(width: 24.w),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWidget() {
|
||||||
|
debugPrint('----loadStatus:$loadingStatus');
|
||||||
|
switch (loadingStatus) {
|
||||||
|
case KtLoadStatusType.loading:
|
||||||
|
return Center(child: CircularProgressIndicator());
|
||||||
|
case KtLoadStatusType.loadFailed:
|
||||||
|
return KtStatusWidget(
|
||||||
|
type: KtErrorStatusType.noNetwork,
|
||||||
|
onPressed: () {
|
||||||
|
_webViewController?.loadUrl(
|
||||||
|
urlRequest: URLRequest(url: WebUri(KtApis.WEB_SITE_SEARCH)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case KtLoadStatusType.loadNoData:
|
||||||
|
return KtStatusWidget(
|
||||||
|
type: KtErrorStatusType.notFound,
|
||||||
|
onPressed: () {
|
||||||
|
_webViewController?.reload();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
529
lib/kt_pages/kt_mine/kt_store/logic.dart
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_pages/kt_mine/kt_store/state.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
|
|
||||||
|
import '../../../dio_cilent/kt_apis.dart';
|
||||||
|
import '../../../dio_cilent/kt_request.dart';
|
||||||
|
import '../../../kt_model/kt_goods_bean.dart';
|
||||||
|
import '../../../kt_model/kt_order_bean.dart';
|
||||||
|
import '../../../kt_model/kt_store_bean.dart';
|
||||||
|
import '../../../kt_utils/kt_device_info_utils.dart';
|
||||||
|
import '../../../kt_utils/kt_iap_util.dart';
|
||||||
|
import '../../../kt_utils/kt_purchase_restore_utils.dart';
|
||||||
|
import '../../../kt_utils/kt_toast_utils.dart';
|
||||||
|
import '../../../kt_utils/kt_utils.dart';
|
||||||
|
import '../logic.dart';
|
||||||
|
|
||||||
|
class KtStoreLogic extends GetxController {
|
||||||
|
final state = KtStoreState();
|
||||||
|
late StreamSubscription<List<PurchaseDetails>> _streamSubscription;
|
||||||
|
final RefreshController refreshController = RefreshController();
|
||||||
|
|
||||||
|
final String appKey = 'kinetra';
|
||||||
|
List<PurchaseDetails> purchaseList = [];
|
||||||
|
Function? func;
|
||||||
|
bool isCoinDialog = false;
|
||||||
|
bool isRaminVipDialog = false; // 是否挽回弹窗
|
||||||
|
bool isCoinDialogOpen = false;
|
||||||
|
|
||||||
|
// 会员金币弹窗是否容易点击
|
||||||
|
bool coinsModalEasyClose = false;
|
||||||
|
bool forcedRecharge = false;
|
||||||
|
String closeLabel = '';
|
||||||
|
Function? pauseVideoFunc;
|
||||||
|
Function? coinDialogCallback;
|
||||||
|
|
||||||
|
@override
|
||||||
|
onReady() {
|
||||||
|
getStoreInfo();
|
||||||
|
initPurchaseListener();
|
||||||
|
super.onReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
KtInAppPurchaseUtil.cancelStreamSubscription(_streamSubscription);
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取store商品列表
|
||||||
|
getStoreInfo() async {
|
||||||
|
EasyLoading.show(
|
||||||
|
status: 'Searching...',
|
||||||
|
maskType: EasyLoadingMaskType.black,
|
||||||
|
dismissOnTap: false,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
ApiResponse res = await KtHttpClient().request(
|
||||||
|
KtApis.paySettingsV4,
|
||||||
|
method: HttpMethod.get,
|
||||||
|
);
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
refreshController.refreshCompleted();
|
||||||
|
if (res.success) {
|
||||||
|
state.storeBean = KtStoreBean.fromJson(res.data);
|
||||||
|
update();
|
||||||
|
initStore();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initStore() async {
|
||||||
|
EasyLoading.show(status: 'Searching...');
|
||||||
|
bool isAvailable = await KtInAppPurchaseUtil.isAvailable();
|
||||||
|
if (!isAvailable) {
|
||||||
|
return KtToastUtils.showError('In App purchase is not available');
|
||||||
|
}
|
||||||
|
// 产品ID
|
||||||
|
List<String> productIds = [];
|
||||||
|
// vipId
|
||||||
|
List<String> vipIds = [];
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
productIds = (state.storeBean.listCoins ?? [])
|
||||||
|
.map((item) => '$appKey.${item.iosTemplateId}')
|
||||||
|
.toList();
|
||||||
|
vipIds = (state.storeBean.listSubVip ?? [])
|
||||||
|
.map((item) => '$appKey.${item.iosTemplateId}')
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
productIds = state.storeBean.listCoins!
|
||||||
|
.map((item) => item.androidTemplateId ?? '')
|
||||||
|
.toList();
|
||||||
|
vipIds = state.storeBean.listSubVip!
|
||||||
|
.map((item) => item.androidTemplateId ?? '')
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productIds.isEmpty && vipIds.isEmpty) {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
KtToastUtils.showError('Pay item is empty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductDetailsResponse productDetailsResponse =
|
||||||
|
await KtInAppPurchaseUtil.queryProducts(<String>{
|
||||||
|
...vipIds,
|
||||||
|
...productIds,
|
||||||
|
});
|
||||||
|
if (productDetailsResponse.error != null) {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
|
||||||
|
debugPrint(
|
||||||
|
"----productDetailsResponse.error${productDetailsResponse.error}",
|
||||||
|
);
|
||||||
|
KtToastUtils.showError(productDetailsResponse.error!.message);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint('---未找到的商品: ${productDetailsResponse.notFoundIDs.join(', ')}');
|
||||||
|
|
||||||
|
debugPrint(
|
||||||
|
'----productDetailsResponse:${productDetailsResponse.productDetails.length}',
|
||||||
|
);
|
||||||
|
if (productDetailsResponse.productDetails.isEmpty) {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
|
||||||
|
KtToastUtils.showError('Query store is empty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
state.storeBean.listCoins!.removeWhere(
|
||||||
|
(a) => productDetailsResponse.productDetails.every((b) {
|
||||||
|
if (b.id == '$appKey.${a.iosTemplateId}') {
|
||||||
|
a.productDetails = b;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
state.storeBean.listSubVip!.removeWhere(
|
||||||
|
(a) => productDetailsResponse.productDetails.every((b) {
|
||||||
|
if (b.id == '$appKey.${a.iosTemplateId}') {
|
||||||
|
a.productDetails = b;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
state.storeBean.listCoins!.removeWhere(
|
||||||
|
(a) => productDetailsResponse.productDetails.every((b) {
|
||||||
|
if (b.id == a.androidTemplateId) {
|
||||||
|
a.productDetails = b;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
state.storeBean.listSubVip!.removeWhere(
|
||||||
|
(a) => productDetailsResponse.productDetails.every((b) {
|
||||||
|
if (b.id == a.androidTemplateId) {
|
||||||
|
a.productDetails = b;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
state.storeBean.listCoins?.sort((a, b) => a.size!.compareTo(b.size!));
|
||||||
|
state.refillCoinList =
|
||||||
|
state.storeBean.listCoins
|
||||||
|
?.where((item) => item.size == 'spread')
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 购买监听器
|
||||||
|
initPurchaseListener() {
|
||||||
|
_streamSubscription = KtInAppPurchaseUtil.purchaseStream.listen(
|
||||||
|
(List<PurchaseDetails> purchaseDetailsList) {
|
||||||
|
purchaseList = purchaseDetailsList;
|
||||||
|
_listenToPurchaseUpdated(purchaseDetailsList);
|
||||||
|
},
|
||||||
|
onDone: () {
|
||||||
|
_streamSubscription.cancel();
|
||||||
|
},
|
||||||
|
onError: (err) {
|
||||||
|
debugPrint('---stream-err:$err');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
|
||||||
|
for (var purchaseDetails in purchaseDetailsList) {
|
||||||
|
debugPrint(
|
||||||
|
'---listen-purchaseDetails:${purchaseDetailsList.length} ${purchaseDetails.productID} ${purchaseDetails.status} ${purchaseDetails.pendingCompletePurchase}',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (purchaseDetails.status == PurchaseStatus.pending) {
|
||||||
|
// 购买中
|
||||||
|
debugPrint('Purchase is pending: ${purchaseDetails.productID}');
|
||||||
|
KtInAppPurchaseUtil.completePurchase(purchaseDetails);
|
||||||
|
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
|
||||||
|
purchaseDetails.status == PurchaseStatus.restored) {
|
||||||
|
// 购买成功或恢复购买
|
||||||
|
debugPrint(
|
||||||
|
'Purchase successful: ${purchaseDetails.productID} ${purchaseDetails.status} ${purchaseDetails.pendingCompletePurchase}',
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
KtGoodsBean? temp =
|
||||||
|
// isCoinDialog
|
||||||
|
// ? state.vipCoinStoreBean
|
||||||
|
// : isRaminVipDialog
|
||||||
|
// ? state.remainVipDialogBean
|
||||||
|
// :
|
||||||
|
[
|
||||||
|
...(state.storeBean.listCoins ?? []),
|
||||||
|
...(state.storeBean.listSubVip ?? []),
|
||||||
|
].firstWhereOrNull(
|
||||||
|
(item) => item.productDetails?.id == purchaseDetails.productID,
|
||||||
|
);
|
||||||
|
if (temp == null) {
|
||||||
|
// UserUtil().reportErrorEvent(
|
||||||
|
// 'platform pay failed',
|
||||||
|
// UserUtil.payError,
|
||||||
|
// payData: {'id': purchaseDetails.productID, 'status': purchaseDetails.status},
|
||||||
|
// errMsg: '未找到匹配商品',
|
||||||
|
// );
|
||||||
|
KtToastUtils.showError('There were some problems with the payment');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
KtGoodsBean? goods = KtGoodsBean.fromJson(temp.toJson());
|
||||||
|
goods.transactionId = purchaseDetails.purchaseID;
|
||||||
|
goods.serverVerificationData =
|
||||||
|
purchaseDetails.verificationData.serverVerificationData;
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
if (KtUtils.isNotEmpty(goods.orderCode)) {
|
||||||
|
bool isSuccess = await verifyPay(goods);
|
||||||
|
if (isSuccess) KtInAppPurchaseUtil.consumeIfNeeded(purchaseDetails);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('--purchase-success-err:$e');
|
||||||
|
}
|
||||||
|
KtInAppPurchaseUtil.completePurchase(purchaseDetails);
|
||||||
|
} else if (purchaseDetails.status == PurchaseStatus.error) {
|
||||||
|
// 购买失败
|
||||||
|
debugPrint('Purchase failed: ${purchaseDetails.error?.message}');
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
KtGoodsBean? goods =
|
||||||
|
[
|
||||||
|
...(state.storeBean.listCoins ?? []),
|
||||||
|
...(state.storeBean.listSubVip ?? []),
|
||||||
|
].firstWhereOrNull(
|
||||||
|
(item) => item.productDetails?.id == purchaseDetails.productID,
|
||||||
|
);
|
||||||
|
// UserUtil().reportErrorEvent(
|
||||||
|
// 'platform pay failed',
|
||||||
|
// UserUtil.payError,
|
||||||
|
// orderCode: goods?.orderCode ?? '',
|
||||||
|
// errMsg: purchaseDetails.error?.toString(),
|
||||||
|
// );
|
||||||
|
debugPrint(
|
||||||
|
'-----itemAlreadyOwned:${purchaseDetails.error?.message.contains('itemAlreadyOwned')}',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (purchaseDetails.error?.message.contains('itemAlreadyOwned') ??
|
||||||
|
false) {
|
||||||
|
KtInAppPurchaseUtil.completePurchase(purchaseDetails, isRetry: true);
|
||||||
|
// KtGoodsBean? goods = [
|
||||||
|
// ...(state.storeBean.listCoins ?? []),
|
||||||
|
// ...(state.storeBean.listSubVip ?? []),
|
||||||
|
// ].firstWhereOrNull((item) => item.productDetails?.id == purchaseDetails.productID);
|
||||||
|
// if (goods?.productDetails != null && count <= 2) {
|
||||||
|
// count++;
|
||||||
|
// KtInAppPurchaseUtil.buy(goods!.productDetails!);
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
KtToastUtils.showError('There were some problems with the payment');
|
||||||
|
}
|
||||||
|
} else if (purchaseDetails.status == PurchaseStatus.canceled) {
|
||||||
|
// 购买取消
|
||||||
|
debugPrint('Purchase canceled: ${purchaseDetails.productID}');
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
KtToastUtils.showToast('User cancel');
|
||||||
|
// KtGoodsBean? goods = [
|
||||||
|
// ...(state.storeBean.listCoins ?? []),
|
||||||
|
// ...(state.storeBean.listSubVip ?? []),
|
||||||
|
// ].firstWhereOrNull((item) => item.productDetails?.id == purchaseDetails.productID);
|
||||||
|
// UserUtil().reportErrorEvent('user pay canceled', "pay_cancel", orderCode: goods?.orderCode ?? '');
|
||||||
|
}
|
||||||
|
KtInAppPurchaseUtil.completePurchase(purchaseDetails, isRetry: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buyGoods(KtGoodsBean payItem, {num? shortPlayId, num? videoId}) {
|
||||||
|
if (payItem.buyType == 'sub_coins') {
|
||||||
|
// showSubCoinCheckDialog(payItem, shortPlayId: shortPlayId, videoId: videoId);
|
||||||
|
} else {
|
||||||
|
createOrder(payItem, shortPlayId: shortPlayId, videoId: videoId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createOrder(KtGoodsBean goods, {num? shortPlayId, num? videoId}) async {
|
||||||
|
EasyLoading.show(status: 'Paying...', maskType: EasyLoadingMaskType.black);
|
||||||
|
// Map<String, dynamic> params = {"pay_setting_id": goods.id!};
|
||||||
|
Map<String, dynamic> params = {
|
||||||
|
"pay_setting_id": goods.id!,
|
||||||
|
"is_discount": 0,
|
||||||
|
};
|
||||||
|
// if (Platform.isIOS && params["is_discount"] == 1) {
|
||||||
|
// final product = goods.productDetails as AppStoreProductDetails;
|
||||||
|
// params.putIfAbsent('product_discount', () => product.skProduct.discounts.first.identifier!);
|
||||||
|
// }
|
||||||
|
if (shortPlayId != null) {
|
||||||
|
params.putIfAbsent('short_play_id', () => shortPlayId);
|
||||||
|
state.shortPlayId = shortPlayId;
|
||||||
|
}
|
||||||
|
if (videoId != null) {
|
||||||
|
params.putIfAbsent('video_id', () => videoId);
|
||||||
|
state.videoId = videoId;
|
||||||
|
}
|
||||||
|
ApiResponse res = await KtHttpClient().request(
|
||||||
|
KtApis.createOrder,
|
||||||
|
data: params,
|
||||||
|
);
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
if (res.success) {
|
||||||
|
if (res.data['code'] == 30007) {
|
||||||
|
KtToastUtils.showError(
|
||||||
|
isCoinDialog
|
||||||
|
? 'You\'ve already subscribed to this coin pack'
|
||||||
|
: 'You are already VIP!',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
KtOrderBean order = KtOrderBean.fromJson(res.data);
|
||||||
|
if (KtUtils.isEmpty(order.orderCode) ||
|
||||||
|
KtUtils.isEmpty(goods.productDetails))
|
||||||
|
return;
|
||||||
|
|
||||||
|
goods.orderCode = order.orderCode;
|
||||||
|
state.orderCode = order.orderCode;
|
||||||
|
// if (isRaminVipDialog) state.remainVipDialogBean.orderCode = order.orderCode;
|
||||||
|
try {
|
||||||
|
// if (goods.discountType != 0) {
|
||||||
|
// if (Platform.isIOS) {
|
||||||
|
// if (goods.discountType == 2) {
|
||||||
|
// final product = goods.productDetails as AppStoreProductDetails;
|
||||||
|
// final purchaseParamSk2 = AppStorePurchaseParam(
|
||||||
|
// productDetails: goods.productDetails!,
|
||||||
|
// applicationUserName: order.discount!.signData!.applicationUsername!,
|
||||||
|
// discount: SKPaymentDiscountWrapper(
|
||||||
|
// identifier: product.skProduct.discounts.first.identifier!,
|
||||||
|
// keyIdentifier: order.discount!.signData!.keyIdentifier!,
|
||||||
|
// nonce: order.discount!.signData!.nonce!,
|
||||||
|
// signature: order.discount!.signData!.signature!,
|
||||||
|
// timestamp: order.discount!.signData!.timestamp!,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// debugPrint('----purchaseParamSk2 identifier:${purchaseParamSk2.discount?.identifier}');
|
||||||
|
// await KtInAppPurchaseUtil.buyDiscount(purchaseParamSk2);
|
||||||
|
// } else {
|
||||||
|
// await KtInAppPurchaseUtil.buy(goods.productDetails!, consumable: goods.buyType == 'coins');
|
||||||
|
// }
|
||||||
|
// } else if (Platform.isAndroid) {
|
||||||
|
// if (goods.productDetails! is GooglePlayProductDetails) {
|
||||||
|
// final offer =
|
||||||
|
// (goods.productDetails! as GooglePlayProductDetails).productDetails.subscriptionOfferDetails!.first;
|
||||||
|
//
|
||||||
|
// debugPrint('----offertoken:${offer.offerIdToken}');
|
||||||
|
// final purchaseParam = GooglePlayPurchaseParam(
|
||||||
|
// productDetails: goods.productDetails!,
|
||||||
|
// offerToken: offer.offerIdToken,
|
||||||
|
// );
|
||||||
|
// await KtInAppPurchaseUtil.buyDiscount(purchaseParam);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (Platform.isAndroid) {
|
||||||
|
// if (goods.productDetails! is GooglePlayProductDetails) {
|
||||||
|
// if ((goods.productDetails! as GooglePlayProductDetails).productDetails.subscriptionOfferDetails != null) {
|
||||||
|
// List<SubscriptionOfferDetailsWrapper> offerList =
|
||||||
|
// (goods.productDetails! as GooglePlayProductDetails).productDetails.subscriptionOfferDetails!;
|
||||||
|
// final offerIdToken = offerList.length > 1 ? offerList[1].offerIdToken : offerList[0].offerIdToken;
|
||||||
|
//
|
||||||
|
// final purchaseParam = GooglePlayPurchaseParam(
|
||||||
|
// productDetails: goods.productDetails!,
|
||||||
|
// offerToken: offerIdToken,
|
||||||
|
// );
|
||||||
|
// await KtInAppPurchaseUtil.buyDiscount(purchaseParam);
|
||||||
|
// } else {
|
||||||
|
// await KtInAppPurchaseUtil.buy(goods.productDetails!, consumable: goods.buyType == 'coins');
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// await KtInAppPurchaseUtil.buy(goods.productDetails!, consumable: goods.buyType == 'coins');
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// await KtInAppPurchaseUtil.buy(goods.productDetails!, consumable: goods.buyType == 'coins');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
await KtInAppPurchaseUtil.buy(
|
||||||
|
goods.productDetails!,
|
||||||
|
consumable: goods.buyType == 'coins',
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
KtToastUtils.showError(
|
||||||
|
'There were some problems with the payment,Please try again!',
|
||||||
|
);
|
||||||
|
debugPrint('---e:$e');
|
||||||
|
// UserUtil().reportErrorEvent(
|
||||||
|
// 'platform pay timeout',
|
||||||
|
// UserUtil.payPlatformTimeout,
|
||||||
|
// orderCode: goods.orderCode,
|
||||||
|
// errMsg: e.toString(),
|
||||||
|
// payData: params,
|
||||||
|
// );
|
||||||
|
|
||||||
|
KtInAppPurchaseUtil.clearFailedPurchases();
|
||||||
|
|
||||||
|
for (var item in purchaseList) {
|
||||||
|
if (item.productID == goods.productDetails?.id) {
|
||||||
|
KtInAppPurchaseUtil.completePurchase(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
KtToastUtils.showError('Failed to create order');
|
||||||
|
goods.orderCode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 校验订单
|
||||||
|
Future<bool> verifyPay(
|
||||||
|
KtGoodsBean goods, {
|
||||||
|
bool isRestore = false,
|
||||||
|
bool isAuto = false,
|
||||||
|
}) async {
|
||||||
|
String url = Platform.isAndroid
|
||||||
|
? KtApis.createGooglePay
|
||||||
|
: KtApis.createApplePay;
|
||||||
|
final deviceInfo = KtDeviceInfoUtil();
|
||||||
|
String transactionId = goods.transactionId ?? "";
|
||||||
|
String serverVerificationData = goods.serverVerificationData ?? "";
|
||||||
|
Map<String, dynamic> params = {
|
||||||
|
'pkg_name': deviceInfo.packageName ?? "",
|
||||||
|
'order_code': goods.orderCode ?? "",
|
||||||
|
'product_id': Platform.isAndroid
|
||||||
|
? goods.androidTemplateId
|
||||||
|
: goods.iosTemplateId,
|
||||||
|
'show_money': goods.price,
|
||||||
|
'pay_setting_id': goods.id.toString(),
|
||||||
|
'transaction_id': transactionId,
|
||||||
|
'purchases_token': serverVerificationData,
|
||||||
|
};
|
||||||
|
// if (isRestore) {
|
||||||
|
// UserUtil().reportErrorEvent(
|
||||||
|
// 'pay restore',
|
||||||
|
// UserUtil.payRestore,
|
||||||
|
// type: isAuto ? 'auto' : 'manual',
|
||||||
|
// orderCode: goods.orderCode,
|
||||||
|
// transactionId: transactionId,
|
||||||
|
// payData: params,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
try {
|
||||||
|
ApiResponse res = await KtHttpClient().request(url, data: params);
|
||||||
|
if (res.success && res.data['status'] == 'success') {
|
||||||
|
if (!isRestore) {
|
||||||
|
KtToastUtils.showSuccess(placeholder: 'Pay Success');
|
||||||
|
Future.delayed(Duration(seconds: 1), () => getStoreInfo());
|
||||||
|
}
|
||||||
|
Get.put(KtMineLogic()).getUserInfo();
|
||||||
|
|
||||||
|
func?.call();
|
||||||
|
// coinDialogCallback?.call();
|
||||||
|
// if (isCoinDialog) {
|
||||||
|
// Get.back();
|
||||||
|
// }
|
||||||
|
|
||||||
|
///todo:测试期间安卓所有商品先存本地,上线前删除本行
|
||||||
|
// if (Platform.isAndroid) KtPurchaseRestoreUtil().cacheFailedGoods(goods);
|
||||||
|
KtPurchaseRestoreUtil().removeGoods(goods);
|
||||||
|
cleanFunc();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
coinDialogCallback?.call();
|
||||||
|
// UserUtil().reportErrorEvent(
|
||||||
|
// 'pay callback failed',
|
||||||
|
// UserUtil.payCallback,
|
||||||
|
// orderCode: goods.orderCode,
|
||||||
|
// transactionId: transactionId,
|
||||||
|
// errMsg: params.toString(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
if (!isRestore) KtPurchaseRestoreUtil().cacheFailedGoods(goods);
|
||||||
|
cleanFunc();
|
||||||
|
return false;
|
||||||
|
} catch (e) {
|
||||||
|
coinDialogCallback?.call();
|
||||||
|
if (!isRestore) KtPurchaseRestoreUtil().cacheFailedGoods(goods);
|
||||||
|
debugPrint('---e:$e');
|
||||||
|
cleanFunc();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanFunc() {
|
||||||
|
func = null;
|
||||||
|
// coinDialogCallback = null;
|
||||||
|
// pauseVideoFunc = null;
|
||||||
|
// if (isCoinDialog) isCoinDialog = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
restorePay() {}
|
||||||
|
}
|
17
lib/kt_pages/kt_mine/kt_store/state.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import '../../../kt_model/kt_goods_bean.dart';
|
||||||
|
import '../../../kt_model/kt_store_bean.dart';
|
||||||
|
|
||||||
|
class KtStoreState {
|
||||||
|
KtStoreBean storeBean = KtStoreBean();
|
||||||
|
|
||||||
|
List vipTypes = ['Monthly', 'Weekly', 'Quarterly', 'Yearly'];
|
||||||
|
List<KtGoodsBean> vipList = [];
|
||||||
|
List<KtGoodsBean> coinList = [];
|
||||||
|
List<KtGoodsBean> spreadCoinList = [];
|
||||||
|
List<KtGoodsBean> refillCoinList = [];
|
||||||
|
int selVip = 0;
|
||||||
|
int selCoin = 0;
|
||||||
|
String? orderCode;
|
||||||
|
num? shortPlayId;
|
||||||
|
num? videoId;
|
||||||
|
}
|
@ -1,4 +1,11 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_pages/kt_mine/kt_store/logic.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_widgets/kt_store_widget.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
|
|
||||||
class KtStorePage extends StatefulWidget {
|
class KtStorePage extends StatefulWidget {
|
||||||
const KtStorePage({super.key});
|
const KtStorePage({super.key});
|
||||||
@ -8,10 +15,82 @@ class KtStorePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _KtStorePageState extends State<KtStorePage> {
|
class _KtStorePageState extends State<KtStorePage> {
|
||||||
|
final logic = Get.put(KtStoreLogic());
|
||||||
|
final state = Get.find<KtStoreLogic>().state;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const Scaffold(
|
return Container(
|
||||||
body: Text('Store', style: TextStyle(color: Colors.black)),
|
color: Color(0xFF1E1E20),
|
||||||
|
padding: EdgeInsets.fromLTRB(15.w, ScreenUtil().statusBarHeight, 15.w, 0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: Image.asset('ic_back_white.png'.ktIcon, width: 24.w),
|
||||||
|
onTap: () => Navigator.of(context).maybePop(),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Store',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
EasyThrottle.throttle(
|
||||||
|
'restore-tap',
|
||||||
|
Duration(seconds: 3),
|
||||||
|
() => logic.restorePay(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'ReStore',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SmartRefresher(
|
||||||
|
controller: logic.refreshController,
|
||||||
|
physics: const ClampingScrollPhysics(),
|
||||||
|
enablePullUp: false,
|
||||||
|
enablePullDown: true,
|
||||||
|
onRefresh: logic.getStoreInfo,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
GetBuilder<KtStoreLogic>(
|
||||||
|
assignId: true,
|
||||||
|
builder: (logic) {
|
||||||
|
return KtStoreWidget(
|
||||||
|
store: state.storeBean,
|
||||||
|
onItemTap: (goods) {
|
||||||
|
EasyThrottle.throttle(
|
||||||
|
'buy',
|
||||||
|
Duration(seconds: 3),
|
||||||
|
() => logic.buyGoods(goods),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_pages/kt_mine/insert_web/kt_order_record_page.dart';
|
||||||
import 'package:flutter_kinetra/kt_pages/kt_mine/logic.dart';
|
import 'package:flutter_kinetra/kt_pages/kt_mine/logic.dart';
|
||||||
import 'package:flutter_kinetra/kt_pages/kt_routes.dart';
|
import 'package:flutter_kinetra/kt_pages/kt_routes.dart';
|
||||||
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||||
@ -54,97 +55,169 @@ class _KtMinePageState extends State<KtMinePage> {
|
|||||||
onRefresh: logic.getUserInfo,
|
onRefresh: logic.getUserInfo,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Stack(
|
||||||
width: ScreenUtil().screenWidth - 30.w,
|
clipBehavior: Clip.none,
|
||||||
margin: EdgeInsets.only(top: 22.w),
|
children: [
|
||||||
padding: EdgeInsets.fromLTRB(6.w, 13.w, 6.w, 6.w),
|
Container(
|
||||||
decoration: BoxDecoration(
|
width: ScreenUtil().screenWidth - 30.w,
|
||||||
image: DecorationImage(
|
margin: EdgeInsets.only(top: 10.w),
|
||||||
image: AssetImage('ic_mine_top_bg.png'.ktIcon),
|
padding: EdgeInsets.fromLTRB(6.w, 13.w, 6.w, 6.w),
|
||||||
fit: BoxFit.fill,
|
decoration: BoxDecoration(
|
||||||
),
|
image: DecorationImage(
|
||||||
),
|
image: AssetImage(
|
||||||
child: Column(
|
'ic_mine_top_bg.png'.ktIcon,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
children: [
|
fit: BoxFit.fill,
|
||||||
Text(
|
|
||||||
'Welcome Back, visitor!',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
child: Column(
|
||||||
padding: EdgeInsets.symmetric(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
vertical: 17.w,
|
children: [
|
||||||
horizontal: 13.w,
|
Row(
|
||||||
),
|
children: [
|
||||||
margin: EdgeInsets.only(top: 13.w),
|
Text(
|
||||||
decoration: BoxDecoration(
|
'Coins',
|
||||||
color: Colors.white,
|
style: TextStyle(
|
||||||
borderRadius: BorderRadius.circular(14.w),
|
fontSize: 12.sp,
|
||||||
),
|
color: Colors.white,
|
||||||
child: Row(
|
fontWeight: FontWeight.w500,
|
||||||
children: [
|
),
|
||||||
Image.asset(
|
),
|
||||||
'ic_avatar.png'.ktIcon,
|
SizedBox(width: 5.w),
|
||||||
width: 56.w,
|
Image.asset(
|
||||||
|
'ic_coin.png'.ktIcon,
|
||||||
|
width: 15.w,
|
||||||
|
),
|
||||||
|
SizedBox(width: 2.w),
|
||||||
|
Text(
|
||||||
|
'${(state.userInfo.coinLeftTotal ?? 0) + (state.userInfo.sendCoinLeftTotal ?? 0)}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 1.w,
|
||||||
|
height: 10.w,
|
||||||
|
margin: EdgeInsets.symmetric(
|
||||||
|
horizontal: 15.w,
|
||||||
|
),
|
||||||
|
color: Colors.white.withValues(
|
||||||
|
alpha: .3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Donate',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5.w),
|
||||||
|
Image.asset(
|
||||||
|
'ic_coin.png'.ktIcon,
|
||||||
|
width: 15.w,
|
||||||
|
),
|
||||||
|
SizedBox(width: 2.w),
|
||||||
|
Text(
|
||||||
|
'${state.userInfo.sendCoinLeftTotal ?? 0}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 17.w,
|
||||||
|
horizontal: 13.w,
|
||||||
),
|
),
|
||||||
SizedBox(width: 13.w),
|
margin: EdgeInsets.only(top: 8.w),
|
||||||
Column(
|
decoration: BoxDecoration(
|
||||||
crossAxisAlignment:
|
color: Colors.white,
|
||||||
CrossAxisAlignment.start,
|
borderRadius: BorderRadius.circular(14.w),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Stack(
|
Image.asset(
|
||||||
alignment: Alignment.bottomCenter,
|
'ic_avatar.png'.ktIcon,
|
||||||
|
width: 56.w,
|
||||||
|
),
|
||||||
|
SizedBox(width: 13.w),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Stack(
|
||||||
'text_bg_discover.png'.ktIcon,
|
alignment: Alignment.bottomCenter,
|
||||||
height: 17.w,
|
children: [
|
||||||
width: 55.w,
|
Image.asset(
|
||||||
),
|
'text_bg_discover.png'.ktIcon,
|
||||||
Padding(
|
height: 17.w,
|
||||||
padding: EdgeInsets.only(
|
width: 55.w,
|
||||||
bottom: 2.w,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
KtUtils.isEmpty(
|
|
||||||
state.userInfo.familyName,
|
|
||||||
)
|
|
||||||
? 'Visitor'
|
|
||||||
: state
|
|
||||||
.userInfo
|
|
||||||
.familyName ??
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'PingFang SC',
|
|
||||||
fontSize: 18.sp,
|
|
||||||
color: Colors.black,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: 2.w,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
KtUtils.isEmpty(
|
||||||
|
state
|
||||||
|
.userInfo
|
||||||
|
.familyName,
|
||||||
|
)
|
||||||
|
? 'Visitor'
|
||||||
|
: state
|
||||||
|
.userInfo
|
||||||
|
.familyName ??
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'PingFang SC',
|
||||||
|
fontSize: 18.sp,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight:
|
||||||
|
FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'ID: ${state.userInfo.customerId}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'PingFang SC',
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: Color(0xFF5E5E5E),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
|
||||||
'ID: ${state.userInfo.customerId}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'PingFang SC',
|
|
||||||
fontSize: 12.sp,
|
|
||||||
color: Color(0xFF5E5E5E),
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: -28.w,
|
||||||
|
child: Image.asset(
|
||||||
|
'ic_mine_ip.png'.ktIcon,
|
||||||
|
width: 80.w,
|
||||||
|
height: 84.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 30.w),
|
SizedBox(height: 15.w),
|
||||||
|
vipView(),
|
||||||
|
SizedBox(height: 20.w),
|
||||||
settingsView(),
|
settingsView(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -158,6 +231,124 @@ class _KtMinePageState extends State<KtMinePage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget vipView() {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed(KtRoutes.store);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: ScreenUtil().screenWidth - 30.w,
|
||||||
|
padding: EdgeInsets.only(left: 20.w, top: 15.w, bottom: 12.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('mine_vip_bg.png'.ktIcon),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'VIP & Coins',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Exclusive Deals Inside',
|
||||||
|
style: TextStyle(
|
||||||
|
color: const Color(0xFF1E1E20).withValues(alpha: .5),
|
||||||
|
fontSize: 10.sp,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.w),
|
||||||
|
margin: EdgeInsets.only(left: 50.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(0xFFA7F62F),
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
border: Border.all(color: Color(0xFF487800)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'GO',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 14.sp,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 3.w),
|
||||||
|
Image.asset('ic_polygon.png'.ktIcon, width: 6.w),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.w),
|
||||||
|
Container(
|
||||||
|
width: 208.w,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 7.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0x7FF2FFEA),
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
border: Border.all(color: Colors.white, width: 1.w),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Image.asset('no_ad.png'.ktIcon, width: 12.w),
|
||||||
|
SizedBox(width: 2.w),
|
||||||
|
Text(
|
||||||
|
'Ad-Free',
|
||||||
|
style: TextStyle(fontSize: 10.sp, color: Color(0xFF5E5E5E)),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 8.w,
|
||||||
|
width: 1.w,
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8.w),
|
||||||
|
color: Color(0xFFE6E6E6),
|
||||||
|
),
|
||||||
|
Image.asset('full_access.png'.ktIcon, width: 12.w),
|
||||||
|
SizedBox(width: 2.w),
|
||||||
|
Text(
|
||||||
|
'Full Access',
|
||||||
|
style: TextStyle(fontSize: 10.sp, color: Color(0xFF5E5E5E)),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 8.w,
|
||||||
|
width: 1.w,
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8.w),
|
||||||
|
color: Color(0xFFE6E6E6),
|
||||||
|
),
|
||||||
|
Image.asset('ic_hd.png'.ktIcon, width: 12.w),
|
||||||
|
SizedBox(width: 2.w),
|
||||||
|
Text(
|
||||||
|
'HD',
|
||||||
|
style: TextStyle(fontSize: 10.sp, color: Color(0xFF5E5E5E)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget settingsView() {
|
Widget settingsView() {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -184,6 +375,11 @@ class _KtMinePageState extends State<KtMinePage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
handleItem(
|
||||||
|
'ic_order_record.png',
|
||||||
|
'Order Record',
|
||||||
|
() => Get.to(() => KtOrderRecordPage()),
|
||||||
|
),
|
||||||
handleItem(
|
handleItem(
|
||||||
'ic_privacy.png',
|
'ic_privacy.png',
|
||||||
'Privacy Policy',
|
'Privacy Policy',
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter_kinetra/kt_pages/kt_home/kt_search_page.dart';
|
import 'package:flutter_kinetra/kt_pages/kt_home/kt_search_page.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_pages/kt_mine/insert_web/kt_order_record_page.dart';
|
||||||
import 'package:get/get_navigation/src/routes/get_route.dart';
|
import 'package:get/get_navigation/src/routes/get_route.dart';
|
||||||
|
|
||||||
import 'kt_main_page/view.dart';
|
import 'kt_main_page/view.dart';
|
||||||
import 'kt_mine/insert_web/wallet_page.dart';
|
|
||||||
import 'kt_mine/kt_about_us_page.dart';
|
import 'kt_mine/kt_about_us_page.dart';
|
||||||
import 'kt_mine/kt_help_center/kt_help_center_detail_page.dart';
|
import 'kt_mine/kt_help_center/kt_help_center_detail_page.dart';
|
||||||
import 'kt_mine/kt_help_center/kt_help_center_list_page.dart';
|
import 'kt_mine/kt_help_center/kt_help_center_list_page.dart';
|
||||||
@ -39,7 +39,7 @@ class KtRoutes {
|
|||||||
preventDuplicates: false,
|
preventDuplicates: false,
|
||||||
),
|
),
|
||||||
GetPage(name: store, page: () => KtStorePage()),
|
GetPage(name: store, page: () => KtStorePage()),
|
||||||
GetPage(name: wallet, page: () => const WalletPage()),
|
GetPage(name: wallet, page: () => const KtOrderRecordPage()),
|
||||||
GetPage(name: aboutUs, page: () => const KtAboutUsPage()),
|
GetPage(name: aboutUs, page: () => const KtAboutUsPage()),
|
||||||
GetPage(name: helpCenter, page: () => const KtHelpCenterPage()),
|
GetPage(name: helpCenter, page: () => const KtHelpCenterPage()),
|
||||||
GetPage(name: helpCenterList, page: () => const KtHelpCenterListPage()),
|
GetPage(name: helpCenterList, page: () => const KtHelpCenterListPage()),
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_pages/kt_mine/logic.dart';
|
||||||
import 'package:flutter_kinetra/kt_pages/kt_short_video/state.dart';
|
import 'package:flutter_kinetra/kt_pages/kt_short_video/state.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_utils/kt_toast_utils.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
@ -10,15 +16,19 @@ import '../../dio_cilent/kt_request.dart';
|
|||||||
import '../../kt_model/kt_video_detail_bean.dart';
|
import '../../kt_model/kt_video_detail_bean.dart';
|
||||||
import '../../kt_utils/kt_device_info_utils.dart';
|
import '../../kt_utils/kt_device_info_utils.dart';
|
||||||
import '../../kt_widgets/kt_status_widget.dart';
|
import '../../kt_widgets/kt_status_widget.dart';
|
||||||
|
import '../../kt_widgets/kt_store_widget.dart';
|
||||||
|
import '../kt_mine/kt_store/logic.dart';
|
||||||
|
|
||||||
class VideoPlayLogic extends GetxController {
|
class VideoPlayLogic extends GetxController {
|
||||||
final state = VideoPlayState();
|
final state = VideoPlayState();
|
||||||
|
|
||||||
late final PageController pageController;
|
late final PageController pageController;
|
||||||
List<VideoPlayerController?> controllers = [];
|
List<VideoPlayerController?> controllers = [];
|
||||||
|
KtLoadStatusType videoStatus = KtLoadStatusType.loading;
|
||||||
|
KtLoadStatusType loadStatusType = KtLoadStatusType.loadSuccess;
|
||||||
int currentIndex = 0;
|
int currentIndex = 0;
|
||||||
bool _disposed = false;
|
bool _disposed = false;
|
||||||
|
final userLogic = Get.put(KtMineLogic());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -63,7 +73,6 @@ class VideoPlayLogic extends GetxController {
|
|||||||
}
|
}
|
||||||
clearCacheCtrl();
|
clearCacheCtrl();
|
||||||
try {
|
try {
|
||||||
// LottieLoading.show(Get.context!);
|
|
||||||
ApiResponse res = await KtHttpClient().request(
|
ApiResponse res = await KtHttpClient().request(
|
||||||
KtApis.getVideoDetails,
|
KtApis.getVideoDetails,
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
@ -80,21 +89,17 @@ class VideoPlayLogic extends GetxController {
|
|||||||
}
|
}
|
||||||
state.episodeList = state.video?.episodeList ?? [];
|
state.episodeList = state.video?.episodeList ?? [];
|
||||||
if (toPage) currentIndex = (state.video?.videoInfo?.episode ?? 1) - 1;
|
if (toPage) currentIndex = (state.video?.videoInfo?.episode ?? 1) - 1;
|
||||||
// for (var video in state.episodeList) {
|
for (var video in state.episodeList) {
|
||||||
// if (video.isLock == true) {
|
if (video.isLock == true) {
|
||||||
// state.curUnlock = state.episodeList.indexOf(video);
|
state.curUnlock = state.episodeList.indexOf(video);
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// reportHistory();
|
|
||||||
controllers = List<VideoPlayerController?>.filled(
|
controllers = List<VideoPlayerController?>.filled(
|
||||||
state.episodeList.length,
|
state.episodeList.length,
|
||||||
null,
|
null,
|
||||||
growable: true,
|
growable: true,
|
||||||
);
|
);
|
||||||
// await _initializeController(currentIndex);
|
|
||||||
// _preloadAdjacentVideos();
|
|
||||||
// if(currentIndex != 0){
|
|
||||||
if (toPage) {
|
if (toPage) {
|
||||||
onPageChanged(
|
onPageChanged(
|
||||||
currentIndex,
|
currentIndex,
|
||||||
@ -104,17 +109,15 @@ class VideoPlayLogic extends GetxController {
|
|||||||
} else {
|
} else {
|
||||||
_initializeController(currentIndex);
|
_initializeController(currentIndex);
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
update();
|
update();
|
||||||
} else {
|
} else {
|
||||||
state.loadStatus = KtLoadStatusType.loadFailed;
|
state.loadStatus = KtLoadStatusType.loadFailed;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
// if (userLogic.state.userInfo.isVip == true) state.curUnlock = 9999;
|
if (userLogic.state.userInfo.isVip == true) state.curUnlock = 9999;
|
||||||
update();
|
update();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// LottieLoading.close();
|
|
||||||
state.loadStatus = KtLoadStatusType.loadFailed;
|
state.loadStatus = KtLoadStatusType.loadFailed;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
@ -152,42 +155,49 @@ class VideoPlayLogic extends GetxController {
|
|||||||
// 暂停当前视频
|
// 暂停当前视频
|
||||||
if (controllers[currentIndex]?.value.isPlaying ?? false) {
|
if (controllers[currentIndex]?.value.isPlaying ?? false) {
|
||||||
await controllers[currentIndex]?.pause();
|
await controllers[currentIndex]?.pause();
|
||||||
// if (isUploadHistorySeconds) uploadHistorySeconds(controllers[currentIndex]?.value.position.inMilliseconds ?? 0);
|
if (isUploadHistorySeconds) {
|
||||||
|
uploadHistorySeconds(
|
||||||
|
controllers[currentIndex]?.value.position.inMilliseconds ?? 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (controllers[currentIndex]?.value.isCompleted ?? false) {
|
if (controllers[currentIndex]?.value.isCompleted ?? false) {
|
||||||
// if (isUploadHistorySeconds) uploadHistorySeconds(0);
|
if (isUploadHistorySeconds) uploadHistorySeconds(0);
|
||||||
if (state.activityId != null) reportActivity();
|
if (state.activityId != null) reportActivity();
|
||||||
}
|
}
|
||||||
if (isToggle) {
|
if (isToggle) {
|
||||||
// loadStatusType = LoadStatusType.loading;
|
loadStatusType = KtLoadStatusType.loading;
|
||||||
update();
|
update();
|
||||||
|
|
||||||
await _initializeController(index);
|
await _initializeController(index);
|
||||||
// loadStatusType = LoadStatusType.loadSuccess;
|
loadStatusType = KtLoadStatusType.loadSuccess;
|
||||||
update();
|
update();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
pageController.jumpToPage(index);
|
pageController.jumpToPage(index);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
currentIndex = index;
|
currentIndex = index;
|
||||||
// if (state.episodeList[index].isLock == true) {
|
if (state.episodeList[index].isLock == true) {
|
||||||
// controllers[index]?.seekTo(Duration(seconds: 0));
|
controllers[index]?.seekTo(Duration(seconds: 0));
|
||||||
// controllers[index]?.pause();
|
controllers[index]?.pause();
|
||||||
// update();
|
update();
|
||||||
// // showUnlockDialog();
|
// EasyThrottle.throttle(
|
||||||
// //
|
// 'show-buy-dialog',
|
||||||
// return;
|
// Duration(milliseconds: 3000),
|
||||||
// }
|
// () => showStoreDialog(state.episodeList[index].coins ?? 0),
|
||||||
|
// );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (controllers[index] != null) {
|
if (controllers[index] != null) {
|
||||||
// if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
||||||
controllers[index]?.play();
|
controllers[index]?.play();
|
||||||
// }
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!isToggle) await _initializeController(index);
|
if (!isToggle) await _initializeController(index);
|
||||||
// if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
||||||
controllers[index]?.play();
|
controllers[index]?.play();
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
controllers[index]?.setPlaybackSpeed(state.currentSpeed);
|
controllers[index]?.setPlaybackSpeed(state.currentSpeed);
|
||||||
// updateHomeVideo();
|
// updateHomeVideo();
|
||||||
@ -231,8 +241,7 @@ class VideoPlayLogic extends GetxController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await controller.initialize();
|
await controller.initialize();
|
||||||
// if (index == currentIndex && (episode.isLock == false || userLogic.state.userInfo.isVip == true)) {
|
if (index == currentIndex && (episode.isLock == false || userLogic.state.userInfo.isVip == true)) {
|
||||||
if (index == currentIndex) {
|
|
||||||
controller.play();
|
controller.play();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
@ -271,6 +280,15 @@ class VideoPlayLogic extends GetxController {
|
|||||||
_releaseUnusedControllers();
|
_releaseUnusedControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void uploadHistorySeconds(int playSeconds) {
|
||||||
|
Map<String, dynamic> params = {
|
||||||
|
"short_play_id": state.shortPlayId,
|
||||||
|
"video_id": state.episodeList[currentIndex].id,
|
||||||
|
"play_seconds": playSeconds > 0 ? playSeconds : 0,
|
||||||
|
};
|
||||||
|
KtHttpClient().request(KtApis.uploadHistorySeconds, data: params);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> likeVideo() async {
|
Future<void> likeVideo() async {
|
||||||
if (state.video == null) return;
|
if (state.video == null) return;
|
||||||
|
|
||||||
@ -286,18 +304,128 @@ class VideoPlayLogic extends GetxController {
|
|||||||
|
|
||||||
state.video?.shortPlayInfo?.isCollect =
|
state.video?.shortPlayInfo?.isCollect =
|
||||||
!(state.video?.shortPlayInfo?.isCollect ?? false);
|
!(state.video?.shortPlayInfo?.isCollect ?? false);
|
||||||
// update(['video-like']);
|
|
||||||
// if (state.isFromDiscover) {
|
|
||||||
// try {
|
|
||||||
// final discoverLogic = Get.put(DiscoverLogic());
|
|
||||||
// discoverLogic.setCollectVideo(
|
|
||||||
// state.video!.shortPlayInfo.shortPlayId!,
|
|
||||||
// isCollect: state.video!.shortPlayInfo.isCollect!,
|
|
||||||
// );
|
|
||||||
// } catch (e) {
|
|
||||||
// debugPrint('---err:$e');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 购买剧集
|
||||||
|
buyVideo(num videoId, num coins, {bool toRecharge = false}) async {
|
||||||
|
ApiResponse res = await KtHttpClient().request(
|
||||||
|
KtApis.buyVideo,
|
||||||
|
data: {"short_play_id": state.shortPlayId, "video_id": videoId},
|
||||||
|
);
|
||||||
|
if (res.success) {
|
||||||
|
String status = res.data['status'];
|
||||||
|
if (status == 'success') {
|
||||||
|
int index = state.episodeList.indexWhere((item) => item.id == videoId);
|
||||||
|
state.episodeList[index].isLock = false;
|
||||||
|
if (index != state.episodeList.length - 1) {
|
||||||
|
state.curUnlock = index + 1;
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
onPageChanged(index, isToggle: true);
|
||||||
|
final mineLogic = Get.put(KtMineLogic());
|
||||||
|
mineLogic.getUserInfo();
|
||||||
|
} else if (res.data['status'] == 'not_enough') {
|
||||||
|
KtToastUtils.showError('Coin not enough');
|
||||||
|
if (toRecharge) showStoreDialog(coins);
|
||||||
|
// if (toRecharge) state.detailBean?.payMode == 1 ? showBuyCoinDialog(coins) : showVipBuyDialog(coins);
|
||||||
|
} else if (status == 'jump') {
|
||||||
|
KtToastUtils.showError('Cannot jump episode');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
KtToastUtils.showError('Purchase failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showStoreDialog(num coins) {
|
||||||
|
Get.put(KtStoreLogic());
|
||||||
|
// EasyThrottle.throttle('restore-short', Duration(seconds: 3), () => BuyUtils.restorePay(showTips: false));
|
||||||
|
Get.bottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
Container(
|
||||||
|
width: ScreenUtil().screenWidth,
|
||||||
|
height: ScreenUtil().screenHeight - kToolbarHeight,
|
||||||
|
padding: EdgeInsets.fromLTRB(15.w, 72.w, 15.w, 10.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(image: AssetImage('recharge_bg.png'.ktIcon)),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Balance',
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: Colors.black),
|
||||||
|
),
|
||||||
|
Image.asset('ic_coin.png'.ktIcon, width: 20.w),
|
||||||
|
GetBuilder<KtMineLogic>(
|
||||||
|
builder: (logic) {
|
||||||
|
return Text(
|
||||||
|
'${(logic.state.userInfo.coinLeftTotal ?? 0) + (logic.state.userInfo.sendCoinLeftTotal ?? 0)}',
|
||||||
|
style: TextStyle(fontSize: 13.sp, color: Colors.black),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(width: 2.w),
|
||||||
|
Image.asset('ic_coin.png'.ktIcon, width: 16.w),
|
||||||
|
Container(
|
||||||
|
width: 1.w,
|
||||||
|
height: 10.w,
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 18.w),
|
||||||
|
color: Color(0xFF5E5E5E),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Unlock:',
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: Colors.black),
|
||||||
|
),
|
||||||
|
Image.asset('ic_coin.png'.ktIcon, width: 20.w),
|
||||||
|
SizedBox(width: 2.w),
|
||||||
|
Text(
|
||||||
|
'$coins',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
GetBuilder<KtStoreLogic>(
|
||||||
|
builder: (logic) {
|
||||||
|
return KtStoreWidget(
|
||||||
|
store: logic.state.storeBean,
|
||||||
|
isStoreDialog: true,
|
||||||
|
onItemTap: (goods) async {
|
||||||
|
logic.func = () {
|
||||||
|
Get.back();
|
||||||
|
fetchData(toPage: false);
|
||||||
|
buyVideo(
|
||||||
|
state.episodeList[currentIndex].id!,
|
||||||
|
coins,
|
||||||
|
toRecharge: false,
|
||||||
|
);
|
||||||
|
// checkVipCoinSub();
|
||||||
|
};
|
||||||
|
EasyThrottle.throttle(
|
||||||
|
'buy',
|
||||||
|
Duration(seconds: 3),
|
||||||
|
() async => await logic.buyGoods(
|
||||||
|
goods,
|
||||||
|
shortPlayId: state.shortPlayId,
|
||||||
|
videoId: state.episodeList[currentIndex].id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ class VideoPlayState {
|
|||||||
String imageUrl = '';
|
String imageUrl = '';
|
||||||
int shortPlayId = -1;
|
int shortPlayId = -1;
|
||||||
num videoId = -1;
|
num videoId = -1;
|
||||||
|
int curUnlock = 999;
|
||||||
int? activityId;
|
int? activityId;
|
||||||
bool isFromDiscover = false;
|
bool isFromDiscover = false;
|
||||||
VideoDetailBean? video;
|
VideoDetailBean? video;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||||
import 'package:flutter_kinetra/kt_utils/kt_utils.dart';
|
import 'package:flutter_kinetra/kt_utils/kt_utils.dart';
|
||||||
@ -10,6 +11,7 @@ import 'package:video_player/video_player.dart';
|
|||||||
import 'package:visibility_detector/visibility_detector.dart';
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
import '../../kt_model/kt_video_detail_bean.dart';
|
import '../../kt_model/kt_video_detail_bean.dart';
|
||||||
|
import '../../kt_utils/kt_toast_utils.dart';
|
||||||
import '../../kt_widgets/kt_network_image.dart';
|
import '../../kt_widgets/kt_network_image.dart';
|
||||||
import '../../kt_widgets/kt_status_widget.dart';
|
import '../../kt_widgets/kt_status_widget.dart';
|
||||||
import '../../kt_widgets/kt_video_progress_bar.dart';
|
import '../../kt_widgets/kt_video_progress_bar.dart';
|
||||||
@ -130,15 +132,14 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
|||||||
if (logic.controllers.isEmpty) return Container();
|
if (logic.controllers.isEmpty) return Container();
|
||||||
|
|
||||||
final controller = logic.controllers[index];
|
final controller = logic.controllers[index];
|
||||||
// final episode = state.episodeList[index];
|
final episode = state.episodeList[index];
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
// 视频播放器
|
// 视频播放器
|
||||||
if (controller != null && controller.value.isInitialized && !isAllOver)
|
if (controller != null && controller.value.isInitialized && !isAllOver)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// if (episode.isLock == true) return;
|
if (episode.isLock == true) return;
|
||||||
|
|
||||||
controller.value.isPlaying
|
controller.value.isPlaying
|
||||||
? controller.pause()
|
? controller.pause()
|
||||||
: controller.play();
|
: controller.play();
|
||||||
@ -155,7 +156,7 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
|||||||
onVisibilityChanged: (VisibilityInfo info) {
|
onVisibilityChanged: (VisibilityInfo info) {
|
||||||
var visiblePercentage = info.visibleFraction * 100;
|
var visiblePercentage = info.visibleFraction * 100;
|
||||||
if (visiblePercentage > 20) {
|
if (visiblePercentage > 20) {
|
||||||
// if (episode.isLock == true) return;
|
if (episode.isLock == true) return;
|
||||||
controller.play();
|
controller.play();
|
||||||
} else {
|
} else {
|
||||||
controller.pause();
|
controller.pause();
|
||||||
@ -202,61 +203,101 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
|||||||
if (!isAllOver) Center(child: CircularProgressIndicator()),
|
if (!isAllOver) Center(child: CircularProgressIndicator()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (controller != null && controller.value.isInitialized && !isAllOver)
|
GestureDetector(
|
||||||
GestureDetector(
|
onTap: () {
|
||||||
onTap: () {
|
if (episode.isLock == true) return;
|
||||||
// if (episode.isLock == true) return;
|
(controller?.value.isPlaying ?? true)
|
||||||
controller.value.isPlaying ? controller.pause() : controller.play();
|
? controller?.pause()
|
||||||
setState(() {});
|
: controller?.play();
|
||||||
},
|
setState(() {});
|
||||||
child: Container(
|
},
|
||||||
width: ScreenUtil().screenWidth,
|
child: Container(
|
||||||
height: ScreenUtil().screenHeight,
|
width: ScreenUtil().screenWidth,
|
||||||
color: Colors.transparent,
|
height: ScreenUtil().screenHeight,
|
||||||
),
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (episode.isLock == true)
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
KtNetworkImage(
|
||||||
|
width: ScreenUtil().screenWidth,
|
||||||
|
height: ScreenUtil().screenHeight,
|
||||||
|
imageUrl: state.imageUrl,
|
||||||
|
placeholder: Image.asset(
|
||||||
|
'bg2.png'.ktIcon,
|
||||||
|
width: ScreenUtil().screenWidth,
|
||||||
|
height: ScreenUtil().screenHeight,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: ScreenUtil().screenWidth,
|
||||||
|
height: ScreenUtil().screenHeight,
|
||||||
|
color: Colors.black.withValues(alpha: .75),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset('lock_white.png'.ktIcon, width: 50.w),
|
||||||
|
SizedBox(height: 26.w),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
EasyThrottle.throttle(
|
||||||
|
'unlock',
|
||||||
|
Duration(seconds: 2),
|
||||||
|
() => logic.buyVideo(
|
||||||
|
episode.id!,
|
||||||
|
episode.coins ?? 0,
|
||||||
|
toRecharge: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 280.w,
|
||||||
|
padding: EdgeInsets.only(top: 17.w, bottom: 28.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('unlock_bg.png'.ktIcon),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset('lock_black.png'.ktIcon, width: 20.w),
|
||||||
|
SizedBox(width: 3.w),
|
||||||
|
Text(
|
||||||
|
state.curUnlock == index
|
||||||
|
? 'Unlocking costs ${episode.coins ?? 0}'
|
||||||
|
: 'Prev.locked',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Color(0xFF1E1E20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Image.asset('ic_coin.png'.ktIcon, width: 18.7.w),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Balance:${logic.userLogic.state.userInfo.coinLeftTotal ?? 0} ",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: Color(0xFFF5F5F5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Image.asset('ic_coin.png'.ktIcon, width: 11.2.w),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
// if (episode.isLock == true)
|
|
||||||
// Container(
|
|
||||||
// width: ScreenUtil().screenWidth,
|
|
||||||
// height: ScreenUtil().screenHeight,
|
|
||||||
// color: Colors.black.withValues(alpha: .75),
|
|
||||||
// child: Center(
|
|
||||||
// child: GestureDetector(
|
|
||||||
// onTap: () {
|
|
||||||
// EasyThrottle.throttle(
|
|
||||||
// 'unlock',
|
|
||||||
// Duration(seconds: 2),
|
|
||||||
// () => logic.buyVideo(
|
|
||||||
// episode.id!,
|
|
||||||
// episode.coins ?? 0,
|
|
||||||
// toRecharge: true,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// child: Container(
|
|
||||||
// width: 260.w,
|
|
||||||
// padding: EdgeInsets.symmetric(vertical: 17.w),
|
|
||||||
// decoration: MyStyles.mainBorder(width: 1, radius: 50),
|
|
||||||
// child: Row(
|
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
// children: [
|
|
||||||
// Image.asset('ic_unlock_lock.png'.ktIcon, width: 20.w),
|
|
||||||
// SizedBox(width: 3.w),
|
|
||||||
// Text(
|
|
||||||
// state.curUnlock == index
|
|
||||||
// ? 'Unlocking costs ${episode.coins ?? 0} coins'
|
|
||||||
// : 'Prev.locked',
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 14.sp,
|
|
||||||
// color: DrColors.mainWhite,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// 顶部返回
|
// 顶部返回
|
||||||
Positioned(
|
Positioned(
|
||||||
top: ScreenUtil().statusBarHeight + 10.w,
|
top: ScreenUtil().statusBarHeight + 10.w,
|
||||||
@ -276,102 +317,105 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
|||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0.w,
|
left: 0.w,
|
||||||
child: Platform.isAndroid
|
child: Container(
|
||||||
? SafeArea(child: bottomView(index))
|
width: ScreenUtil().screenWidth,
|
||||||
: bottomView(index),
|
padding: EdgeInsets.fromLTRB(15.w, 40.w, 15.w, 40.w),
|
||||||
),
|
alignment: Alignment.bottomCenter,
|
||||||
],
|
decoration: BoxDecoration(
|
||||||
);
|
gradient: LinearGradient(
|
||||||
}
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
Widget bottomView(int index) {
|
colors: [
|
||||||
return Container(
|
Color(0xFF001D1F).withValues(alpha: 0),
|
||||||
width: ScreenUtil().screenWidth,
|
Color(0xFF001D1F),
|
||||||
padding: EdgeInsets.fromLTRB(15.w, 40.w, 15.w, 50.w),
|
],
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [Color(0xFF001D1F).withValues(alpha: 0), Color(0xFF001D1F)],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: ScreenUtil().screenWidth - 100.w,
|
|
||||||
child: Text(
|
|
||||||
state.video?.shortPlayInfo?.name ?? '',
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.sp,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
child: Column(
|
||||||
if (logic.controllers[index] != null)
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CustomVideoProgressBar(
|
children: [
|
||||||
controller: logic.controllers[index]!,
|
SizedBox(
|
||||||
width: ScreenUtil().screenWidth,
|
width: ScreenUtil().screenWidth - 100.w,
|
||||||
),
|
child: Text(
|
||||||
SizedBox(height: 16.w),
|
state.video?.shortPlayInfo?.name ?? '',
|
||||||
Row(
|
maxLines: 1,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
overflow: TextOverflow.ellipsis,
|
||||||
children: [
|
style: TextStyle(
|
||||||
GestureDetector(
|
fontSize: 16.sp,
|
||||||
onTap: showEpSelDialog,
|
color: Colors.white,
|
||||||
child: Container(
|
fontWeight: FontWeight.w500,
|
||||||
width: 300.w,
|
),
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 12.w,
|
|
||||||
vertical: 9.w,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(50.w),
|
|
||||||
color: Colors.white.withValues(alpha: .1),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Image.asset('ic_ep.png'.ktIcon, width: 16.sp),
|
|
||||||
SizedBox(width: 6.w),
|
|
||||||
Text(
|
|
||||||
'EP.${index + 1}',
|
|
||||||
style: TextStyle(fontSize: 13.sp, color: Colors.white),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
'All ${state.video?.shortPlayInfo?.episodeTotal ?? 0} Episodes',
|
|
||||||
style: TextStyle(fontSize: 13.sp, color: Colors.white),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (logic.controllers[index] != null)
|
||||||
GetBuilder<VideoPlayLogic>(
|
CustomVideoProgressBar(
|
||||||
id: 'video-like',
|
controller: logic.controllers[index]!,
|
||||||
builder: (c) {
|
width: ScreenUtil().screenWidth,
|
||||||
return GestureDetector(
|
),
|
||||||
onTap: () => logic.likeVideo(),
|
SizedBox(height: 16.w),
|
||||||
child: Column(
|
Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Image.asset(
|
children: [
|
||||||
state.video?.shortPlayInfo?.isCollect == true
|
GestureDetector(
|
||||||
? 'ic_collect_sel.png'.ktIcon
|
onTap: showEpSelDialog,
|
||||||
: 'ic_collect_unsel.png'.ktIcon,
|
child: Container(
|
||||||
width: 32.w,
|
width: 300.w,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.w,
|
||||||
|
vertical: 9.w,
|
||||||
),
|
),
|
||||||
],
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(50.w),
|
||||||
|
color: Colors.white.withValues(alpha: .1),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Image.asset('ic_ep.png'.ktIcon, width: 16.sp),
|
||||||
|
SizedBox(width: 6.w),
|
||||||
|
Text(
|
||||||
|
'EP.${index + 1}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
'All ${state.video?.shortPlayInfo?.episodeTotal ?? 0} Episodes',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
GetBuilder<VideoPlayLogic>(
|
||||||
},
|
id: 'video-like',
|
||||||
),
|
builder: (c) {
|
||||||
],
|
return GestureDetector(
|
||||||
|
onTap: () => logic.likeVideo(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
state.video?.shortPlayInfo?.isCollect == true
|
||||||
|
? 'ic_collect_sel.png'.ktIcon
|
||||||
|
: 'ic_collect_unsel.png'.ktIcon,
|
||||||
|
width: 32.w,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,11 +678,13 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.back();
|
Get.back();
|
||||||
// bool? beforeEpUnlock = index - 2 <= 0 ? false : state.episodeList[index - 2].isLock;
|
bool? beforeEpUnlock = index - 1 <= 0
|
||||||
// if (beforeEpUnlock ?? false) {
|
? false
|
||||||
// ToastUtils.show('Unable to unlock episodes');
|
: state.episodeList[index - 1].isLock;
|
||||||
// return;
|
if (beforeEpUnlock ?? false) {
|
||||||
// }
|
KtToastUtils.showError('Unable to unlock episodes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
logic.onPageChanged(index, isToggle: true);
|
logic.onPageChanged(index, isToggle: true);
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@ -665,27 +711,15 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// if (state.episodeList[episode - 1].isLock == true)
|
if (state.episodeList[index].isLock == true)
|
||||||
// Positioned(
|
Positioned(
|
||||||
// right: 0,
|
right: 5.w,
|
||||||
// top: 0,
|
top: 5.w,
|
||||||
// child: Container(
|
child: Image.asset(
|
||||||
// width: 20.w,
|
'ic_lock.png'.ktIcon,
|
||||||
// height: 12.w,
|
width: 12.w,
|
||||||
// decoration: BoxDecoration(
|
),
|
||||||
// color: ColorResource.mainYellow,
|
),
|
||||||
// borderRadius: BorderRadius.only(
|
|
||||||
// topRight: Radius.circular(6.w),
|
|
||||||
// bottomLeft: Radius.circular(6.w),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// child: Image.asset(
|
|
||||||
// 'ic_video_lock.png'.icon,
|
|
||||||
// width: 10.w,
|
|
||||||
// height: 10.w,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
127
lib/kt_utils/kt_iap_util.dart
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
|
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
|
||||||
|
import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
|
||||||
|
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
|
||||||
|
|
||||||
|
// 内购工具类
|
||||||
|
class KtInAppPurchaseUtil {
|
||||||
|
static final InAppPurchase _iap = InAppPurchase.instance;
|
||||||
|
|
||||||
|
/// 查询可用商品
|
||||||
|
static Future<ProductDetailsResponse> queryProducts(Set<String> productIds) async {
|
||||||
|
return await _iap.queryProductDetails(productIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 购买商品(自动区分消耗型/非消耗型)
|
||||||
|
static Future<void> buy(ProductDetails product, {bool consumable = false}) async {
|
||||||
|
final purchaseParam = PurchaseParam(productDetails: product);
|
||||||
|
if (consumable) {
|
||||||
|
await _iap.buyConsumable(purchaseParam: purchaseParam, autoConsume: Platform.isIOS);
|
||||||
|
} else {
|
||||||
|
await _iap.buyNonConsumable(purchaseParam: purchaseParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 监听购买更新
|
||||||
|
static Stream<List<PurchaseDetails>> get purchaseStream => _iap.purchaseStream;
|
||||||
|
|
||||||
|
/// 完成购买(消耗型商品)
|
||||||
|
static Future<void> completePurchase(PurchaseDetails purchase, {bool isRetry = false}) async {
|
||||||
|
if (purchase.pendingCompletePurchase) {
|
||||||
|
if (isRetry) {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
try {
|
||||||
|
await _iap.completePurchase(purchase);
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('---err:${e}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await _iap.completePurchase(purchase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (Platform.isAndroid) {
|
||||||
|
// final InAppPurchaseAndroidPlatformAddition addition =
|
||||||
|
// InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition;
|
||||||
|
// if (purchase.productID.contains('coins')) {
|
||||||
|
// await addition.consumePurchase(purchase);
|
||||||
|
// }
|
||||||
|
// QueryPurchaseDetailsResponse response = await addition.queryPastPurchases();
|
||||||
|
// for (GooglePlayPurchaseDetails purchaseDetails in response.pastPurchases) {
|
||||||
|
// _iap.completePurchase(purchaseDetails);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (purchase.pendingCompletePurchase) {
|
||||||
|
// await _iap.completePurchase(purchase);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> consumeIfNeeded(PurchaseDetails purchaseDetails) async {
|
||||||
|
if (purchaseDetails is GooglePlayPurchaseDetails) {
|
||||||
|
final InAppPurchaseAndroidPlatformAddition addition =
|
||||||
|
InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition;
|
||||||
|
await addition.consumePurchase(purchaseDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查是否可用
|
||||||
|
static Future<bool> isAvailable() async {
|
||||||
|
return await _iap.isAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 恢复购买(非消耗型商品)
|
||||||
|
static Future<void> restorePurchases() async {
|
||||||
|
await _iap.restorePurchases();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 校验购买状态
|
||||||
|
static bool isPurchaseSuccess(PurchaseDetails purchase) {
|
||||||
|
return purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 取消订阅流(如需手动管理)
|
||||||
|
static void cancelStreamSubscription(StreamSubscription? sub) {
|
||||||
|
sub?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 完成错误的购买
|
||||||
|
// static void completeFailed() {
|
||||||
|
// _iap.clearPendingPurchases();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @visibleForTesting
|
||||||
|
static Future<void> clearFailedPurchases() async {
|
||||||
|
if (Platform.isIOS || Platform.isMacOS) {
|
||||||
|
final wrapper = SKPaymentQueueWrapper();
|
||||||
|
final transactions = await wrapper.transactions();
|
||||||
|
for (final transaction in transactions) {
|
||||||
|
// if (transaction.transactionState == SKPaymentTransactionStateWrapper.failed) {
|
||||||
|
await wrapper.finishTransaction(transaction);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取未完成的购买(通过监听 purchaseStream)
|
||||||
|
static Future<List<PurchaseDetails>> getPendingPurchases() async {
|
||||||
|
final available = await isAvailable();
|
||||||
|
if (!available) return [];
|
||||||
|
final List<PurchaseDetails> pending = [];
|
||||||
|
// 只获取一次当前流数据
|
||||||
|
final completer = Completer<List<PurchaseDetails>>();
|
||||||
|
final sub = purchaseStream.listen((purchases) {
|
||||||
|
pending.addAll(purchases.where((p) => p.status == PurchaseStatus.pending));
|
||||||
|
completer.complete(pending);
|
||||||
|
});
|
||||||
|
final result = await completer.future;
|
||||||
|
await sub.cancel();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
48
lib/kt_utils/kt_purchase_restore_utils.dart
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../kt_model/kt_goods_bean.dart';
|
||||||
|
|
||||||
|
class KtPurchaseRestoreUtil {
|
||||||
|
static final KtPurchaseRestoreUtil _instance = KtPurchaseRestoreUtil._internal();
|
||||||
|
|
||||||
|
factory KtPurchaseRestoreUtil() => _instance;
|
||||||
|
|
||||||
|
KtPurchaseRestoreUtil._internal();
|
||||||
|
|
||||||
|
static const String _cacheKey = 'restore_goods_list';
|
||||||
|
|
||||||
|
/// 保存失败的goods到本地缓存
|
||||||
|
Future<void> cacheFailedGoods(KtGoodsBean goods) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
List<String> goodsList = prefs.getStringList(_cacheKey) ?? [];
|
||||||
|
goodsList.add(json.encode(goods.toJson()));
|
||||||
|
print('-----goods.toJson:${goods.toJson()}');
|
||||||
|
await prefs.setStringList(_cacheKey, goodsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 读取本地缓存的goods列表
|
||||||
|
Future<List<KtGoodsBean>> getCachedGoodsList() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
List<String> goodsList = prefs.getStringList(_cacheKey) ?? [];
|
||||||
|
return goodsList.map((e) => KtGoodsBean.fromJson(json.decode(e))).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清除本地缓存
|
||||||
|
Future<void> clearCachedGoods() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove(_cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 移除指定goods
|
||||||
|
Future<void> removeGoods(KtGoodsBean goods) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
List<String> goodsList = prefs.getStringList(_cacheKey) ?? [];
|
||||||
|
goodsList.removeWhere((e) {
|
||||||
|
final g = KtGoodsBean.fromJson(json.decode(e));
|
||||||
|
return g.id == goods.id;
|
||||||
|
});
|
||||||
|
await prefs.setStringList(_cacheKey, goodsList);
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,7 @@ final class KtToastUtils {
|
|||||||
static CancelFunc showError(String msg) {
|
static CancelFunc showError(String msg) {
|
||||||
return showToast(
|
return showToast(
|
||||||
msg,
|
msg,
|
||||||
icon: Icon(Icons.error, size: 13, color: Colors.white),
|
icon: Icon(Icons.error, size: 13, color: Colors.black),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
364
lib/kt_widgets/kt_store_widget.dart
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_model/kt_goods_bean.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
|
import '../kt_model/kt_store_bean.dart';
|
||||||
|
|
||||||
|
class KtStoreWidget extends StatefulWidget {
|
||||||
|
final KtStoreBean store;
|
||||||
|
final bool onlyCoins;
|
||||||
|
final bool isStoreDialog;
|
||||||
|
final void Function(KtGoodsBean goods)? onItemTap; // 新增回调
|
||||||
|
|
||||||
|
const KtStoreWidget({
|
||||||
|
super.key,
|
||||||
|
required this.store,
|
||||||
|
this.onItemTap,
|
||||||
|
this.isStoreDialog = false,
|
||||||
|
this.onlyCoins = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<KtStoreWidget> createState() => _KtStoreWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _KtStoreWidgetState extends State<KtStoreWidget> {
|
||||||
|
int selVip = -1;
|
||||||
|
int selCoin = -1;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: initWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget descView() {
|
||||||
|
return Text('''
|
||||||
|
1. Coins are virtual items and cannot be refunded. Use it for this product. \n
|
||||||
|
2. Gold coins will never expire, the reward coins will expire 24 hours a day.\n
|
||||||
|
3. Coins will be used first when unlocking episodes. If the amount is insufficient, reward coins will automatically be used. \n
|
||||||
|
4. The purchase has not been credited, click <Restore> to refresh. \n
|
||||||
|
5. For other questions, contact us via Profile>Help &feedback.\n
|
||||||
|
''', style: TextStyle(fontSize: 12.sp, color: Color(0xFF848484),height: 0.95));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> initWidget() {
|
||||||
|
// if (widget.onlyCoins) return [coinList()];
|
||||||
|
List<Widget> widgets = [SizedBox(height: 10.w)];
|
||||||
|
//
|
||||||
|
// int coinsIndex = widget.store.sort?.indexOf('list_coins') ?? 0;
|
||||||
|
// int vipIndex = widget.store.sort?.indexOf('list_sub_vip') ?? 0;
|
||||||
|
// if (coinsIndex < vipIndex) {
|
||||||
|
// widgets.addAll([coinList(), SizedBox(height: 15.w), vipList()]);
|
||||||
|
// } else {
|
||||||
|
// widgets.addAll([vipList(), SizedBox(height: 15.w), coinList()]);
|
||||||
|
// }
|
||||||
|
widgets.addAll([coinList()]);
|
||||||
|
|
||||||
|
widgets.addAll([
|
||||||
|
if (widget.store.payMode == 0 && widget.store.showType == 0)
|
||||||
|
SizedBox(height: 15.w),
|
||||||
|
descView(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return widgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<KtGoodsBean> get smallList =>
|
||||||
|
widget.store.listCoins!.where((item) => item.size == 'small').toList();
|
||||||
|
|
||||||
|
List<KtGoodsBean> get bigList =>
|
||||||
|
widget.store.listCoins!.where((item) => item.size == 'big').toList();
|
||||||
|
|
||||||
|
Widget coinList() {
|
||||||
|
if (widget.store.listCoins?.isEmpty ?? true) return Container();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (!widget.onlyCoins)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Go Coins',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color:widget.isStoreDialog? Color(0xFF1E1E20):Colors.white,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
' | Limited-time coin packs',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color:widget.isStoreDialog? Color(0xFF1E1E20):Colors.white,
|
||||||
|
fontWeight: FontWeight.w200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.w),
|
||||||
|
// big
|
||||||
|
// if (!onlyNewSubCoins)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
...bigList.map((item) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
selCoin = item.id ?? -1;
|
||||||
|
setState(() {});
|
||||||
|
widget.onItemTap?.call(item); // 回调
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 167.w,
|
||||||
|
padding: EdgeInsets.fromLTRB(4.w, 17.w, 5.w, 3.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('coin_big_bg.png'.ktIcon),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'ic_coin.png'.ktIcon,
|
||||||
|
width: 22.4.w,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${item.coins ?? 0}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
color: Color(0xFF1E1E20),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4.w),
|
||||||
|
if ((item.sendCoins ?? 0) > 0)
|
||||||
|
Text(
|
||||||
|
'+${item.sendCoins}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFFAD8502),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 6.w),
|
||||||
|
Container(
|
||||||
|
width: 160.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 6.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(0xFF1E1E20),
|
||||||
|
borderRadius: BorderRadius.circular(8.w),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${item.productDetails?.price ?? 0}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
color: Color(0xFFFFD321),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!item.cornerMarker.isNullString)
|
||||||
|
Positioned(
|
||||||
|
top: 4.w,
|
||||||
|
left: 4.w,
|
||||||
|
child: Image.asset(
|
||||||
|
'ic_fire.png'.ktIcon,
|
||||||
|
width: 16.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if ((item.sendCoins ?? 0) > 0)
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
child: Container(
|
||||||
|
width: 42.w,
|
||||||
|
height: 16.w,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 3.w),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment(0.50, 0.00),
|
||||||
|
end: Alignment(0.50, 1.00),
|
||||||
|
colors: [
|
||||||
|
const Color(0xFFFD9A68),
|
||||||
|
const Color(0xFFFF538D),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8.w),
|
||||||
|
topRight: Radius.circular(8.w),
|
||||||
|
bottomLeft: Radius.circular(8.w),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'+${((item.sendCoins ?? 0) / (item.coins ?? 1) * 100).toInt()}%',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.w),
|
||||||
|
// if (!widget.onlyCoins) subCoinList(),
|
||||||
|
// small
|
||||||
|
// if (!onlyNewSubCoins)
|
||||||
|
GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
mainAxisSpacing: 10.w,
|
||||||
|
crossAxisSpacing: 10.w,
|
||||||
|
childAspectRatio: 108 / 90,
|
||||||
|
),
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
var item = smallList[index];
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
selCoin = item.id ?? -1;
|
||||||
|
setState(() {});
|
||||||
|
widget.onItemTap?.call(item); // 回调
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 108.w,
|
||||||
|
padding: EdgeInsets.fromLTRB(5.w, 14.w, 5.w, 4.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('coin_small_bg.png'.ktIcon),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'ic_coin.png'.ktIcon,
|
||||||
|
width: 22.4.w,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${item.coins ?? 0}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
color: Color(0xFF1E1E20),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
height: 0.8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if ((item.sendCoins ?? 0) > 0)
|
||||||
|
Text(
|
||||||
|
'+${item.sendCoins}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Color(0xFF499C00),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 4.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(0xFF1E1E20),
|
||||||
|
borderRadius: BorderRadius.circular(8.w),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${item.productDetails?.price ?? 0}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
color: Color(0xFF80FF00),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!item.cornerMarker.isNullString)
|
||||||
|
Positioned(
|
||||||
|
top: 4.w,
|
||||||
|
left: 4.w,
|
||||||
|
child: Image.asset('ic_fire.png'.ktIcon, width: 16.w),
|
||||||
|
),
|
||||||
|
if ((item.sendCoins ?? 0) > 0)
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
child: Container(
|
||||||
|
width: 42.w,
|
||||||
|
height: 16.w,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 3.w),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment(0.50, 0.00),
|
||||||
|
end: Alignment(0.50, 1.00),
|
||||||
|
colors: [
|
||||||
|
const Color(0xFFFD9A68),
|
||||||
|
const Color(0xFFFF538D),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(8.w),
|
||||||
|
topRight: Radius.circular(8.w),
|
||||||
|
bottomLeft: Radius.circular(8.w),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'+${((item.sendCoins ?? 0) / (item.coins ?? 1) * 100).toInt()}%',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: smallList.length,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,6 @@ import 'package:wakelock_plus/wakelock_plus.dart';
|
|||||||
|
|
||||||
import 'kt_pages/kt_routes.dart';
|
import 'kt_pages/kt_routes.dart';
|
||||||
import 'kt_utils/kt_device_info_utils.dart';
|
import 'kt_utils/kt_device_info_utils.dart';
|
||||||
import 'kt_utils/kt_keys.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|