feat:iOS一期
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
|
||||
|
||||
别名: com.kinetra.adehok.app
|
||||
创建日期: 2025年8月20日
|
||||
创建日期: 2025年9月22日
|
||||
条目类型: PrivateKeyEntry
|
||||
证书链长度: 1
|
||||
证书[1]:
|
||||
所有者: CN=zy, OU=qj, O=qj, L=cs, ST=hn, C=cn
|
||||
发布者: CN=zy, OU=qj, O=qj, L=cs, ST=hn, C=cn
|
||||
序列号: 45fb9110ec85ed35
|
||||
生效时间: Wed Aug 20 13:44:03 CST 2025, 失效时间: Sun Jan 05 13:44:03 CST 2053
|
||||
所有者: CN=qj, OU=qj, O=qj, L=cs, ST=hunan, C=cn
|
||||
发布者: CN=qj, OU=qj, O=qj, L=cs, ST=hunan, C=cn
|
||||
序列号: f98a0bc4e952426a
|
||||
生效时间: 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
|
||||
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
|
||||
SHA1: 73:61:84:4F:97:9C:EC:B6:5C:25:64:E9:98:51:2C:2E:67:07:1F:AC
|
||||
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
|
||||
主体公共密钥算法: 2048 位 RSA 密钥
|
||||
版本: 3
|
||||
|
||||
# 监听model修改,自动生成
|
||||
dart run build_runner watch --delete-conflicting-outputs
|
||||
|
@ -4,6 +4,11 @@ plugins {
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
val signingProperties =
|
||||
project.rootProject.file("key.properties").takeIf { it.exists() }
|
||||
?.reader()
|
||||
?.use { Properties().apply { load(it) } }
|
||||
?: Properties()
|
||||
|
||||
android {
|
||||
namespace = "com.kinetra.adehok.app"
|
||||
@ -29,13 +34,44 @@ android {
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
|
||||
ndk {
|
||||
abiFilters.clear()
|
||||
abiFilters.add("armeabi-v7a")
|
||||
abiFilters.add("arm64-v8a")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
storeFile = file(signingProperties["storeFile"].toString())
|
||||
storePassword = signingProperties["storePassword"].toString()
|
||||
keyAlias = signingProperties["keyAlias"].toString()
|
||||
keyPassword = signingProperties["keyPassword"].toString()
|
||||
}
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
isEnable = false
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
|
||||
// 签名配置
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
|
||||
}
|
||||
debug {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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 |
@ -93,7 +93,7 @@ class KtApis {
|
||||
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";
|
||||
|
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());
|
@ -154,7 +154,7 @@ class SignInActivityPageState extends State<KtSearchPage> with RouteAware {
|
||||
allowedOriginRules: {"*"},
|
||||
onPostMessage:
|
||||
(message, sourceOrigin, isMainFrame, replyProxy) {
|
||||
print('----callback--jsonS:$message');
|
||||
debugPrint('----callback--jsonS:$message');
|
||||
if (message?.data != null) {
|
||||
_handleWebMessage(message?.data);
|
||||
}
|
||||
@ -247,7 +247,7 @@ class SignInActivityPageState extends State<KtSearchPage> with RouteAware {
|
||||
}
|
||||
|
||||
Widget _buildWidget() {
|
||||
print('----loadStatus:$loadingStatus');
|
||||
debugPrint('----loadStatus:$loadingStatus');
|
||||
switch (loadingStatus) {
|
||||
case KtLoadStatusType.loading:
|
||||
return Center(child: CircularProgressIndicator());
|
||||
|
@ -4,7 +4,6 @@ import 'package:flutter_kinetra/kt_pages/kt_actor/kt_actor_page.dart';
|
||||
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
import '../../kt_utils/kt_keys.dart';
|
||||
import '../kt_explore/view.dart';
|
||||
import '../kt_home/view.dart';
|
||||
import '../kt_mine/view.dart';
|
||||
@ -23,7 +22,7 @@ class _KtMainPageState extends State<KtMainPage>
|
||||
static const List _tabsTitle = [
|
||||
{'icon': 'home', 'title': 'Home'},
|
||||
{'icon': 'explore', 'title': 'Explore'},
|
||||
{'icon': 'actor', 'title': 'Actor'},
|
||||
{'icon': 'actor', 'title': 'Actors'},
|
||||
{'icon': 'favorite', 'title': 'My List'},
|
||||
{'icon': 'mine', 'title': 'Profile'},
|
||||
];
|
||||
|
@ -12,20 +12,21 @@ import 'package:get/get.dart';
|
||||
import '../../../dio_cilent/kt_apis.dart';
|
||||
import '../../../kt_utils/kt_keys.dart';
|
||||
import '../../../kt_utils/kt_utils.dart';
|
||||
import '../../../kt_widgets/kt_status_widget.dart';
|
||||
import '../../../main.dart';
|
||||
|
||||
class WalletPage extends StatefulWidget {
|
||||
const WalletPage({super.key});
|
||||
class KtOrderRecordPage extends StatefulWidget {
|
||||
const KtOrderRecordPage({super.key});
|
||||
|
||||
@override
|
||||
SignInActivityPageState createState() => SignInActivityPageState();
|
||||
KtOrderRecordPageState createState() => KtOrderRecordPageState();
|
||||
}
|
||||
|
||||
class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
||||
class KtOrderRecordPageState extends State<KtOrderRecordPage> with RouteAware {
|
||||
InAppWebViewController? _webViewController;
|
||||
late PullToRefreshController _webRefreshController;
|
||||
late Map<String, String> _userData;
|
||||
// LoadStatusType loadingStatus = LoadStatusType.loading;
|
||||
KtLoadStatusType loadingStatus = KtLoadStatusType.loading;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -39,10 +40,10 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
||||
'time_zone': KtUtils.getTimeZoneOffset(DateTime.now()),
|
||||
'type': Platform.isAndroid ? 'android' : 'ios',
|
||||
'lang': 'en',
|
||||
'theme': 'theme_7',
|
||||
// 'theme': 'theme_7',
|
||||
'token': SpUtil.getString(KtKeys.token) ?? '',
|
||||
};
|
||||
print('-----userData:${_userData}');
|
||||
debugPrint('-----userData:$_userData');
|
||||
}
|
||||
|
||||
_initRefreshController() {
|
||||
@ -108,32 +109,24 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
// appBar: AppBar(
|
||||
// leading: Container(
|
||||
// padding: EdgeInsets.only(left: 15.w, bottom: 6.w),
|
||||
// child: IconButton(
|
||||
// icon: Image.asset('ic_back.png'.ktIcon, width: 24.w),
|
||||
// onPressed: () => Navigator.of(context).maybePop(),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
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,
|
||||
),
|
||||
return 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: Stack(
|
||||
children: [
|
||||
InAppWebView(
|
||||
),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(top: 16.w),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 20.w),
|
||||
child: InAppWebView(
|
||||
pullToRefreshController: _webRefreshController,
|
||||
initialSettings: InAppWebViewSettings(
|
||||
cacheEnabled: false,
|
||||
@ -156,7 +149,7 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
||||
return window.flutter_inappwebview.callHandler('js2app',jsonS);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
""",
|
||||
injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
|
||||
),
|
||||
@ -167,37 +160,26 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
||||
handlerName: 'getUserInfo',
|
||||
callback: (_) => jsonEncode(_userData),
|
||||
);
|
||||
if (Platform.isIOS) {
|
||||
// ios
|
||||
await _webViewController?.addWebMessageListener(
|
||||
WebMessageListener(
|
||||
jsObjectName: "js2app",
|
||||
allowedOriginRules: {"*"},
|
||||
onPostMessage:
|
||||
(message, sourceOrigin, isMainFrame, replyProxy) {
|
||||
if (message?.data != null) {
|
||||
_handleWebMessage(message?.data);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if (Platform.isAndroid) {
|
||||
_webViewController?.addJavaScriptHandler(
|
||||
handlerName: "js2app",
|
||||
callback: (jsonS) {
|
||||
if (jsonS.isNotEmpty) {
|
||||
_handleWebMessage(jsonS.toString());
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
await _webViewController?.addWebMessageListener(
|
||||
WebMessageListener(
|
||||
jsObjectName: "goStore",
|
||||
allowedOriginRules: {"*"},
|
||||
onPostMessage:
|
||||
(message, sourceOrigin, isMainFrame, replyProxy) {
|
||||
debugPrint('----callback--jsonS:$message');
|
||||
if (message != null) {
|
||||
Get.toNamed(KtRoutes.store);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
await _webViewController?.loadUrl(
|
||||
urlRequest: URLRequest(url: WebUri(KtApis.WEB_SITE_WALLET)),
|
||||
);
|
||||
},
|
||||
onLoadStart: (controller, url) {
|
||||
setState(() {
|
||||
// loadingStatus = LoadStatusType.loading;
|
||||
loadingStatus = KtLoadStatusType.loading;
|
||||
});
|
||||
},
|
||||
onLoadStop: (controller, url) async {
|
||||
@ -213,25 +195,25 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
||||
controller.evaluateJavascript(
|
||||
source:
|
||||
'''
|
||||
if(typeof window.receiveDataFromNative === 'function') {
|
||||
window.receiveDataFromNative($userJsonStr);
|
||||
}
|
||||
''',
|
||||
if(typeof window.receiveDataFromNative === 'function') {
|
||||
window.receiveDataFromNative($userJsonStr);
|
||||
}
|
||||
''',
|
||||
);
|
||||
});
|
||||
} else if (Platform.isAndroid) {
|
||||
await controller.evaluateJavascript(
|
||||
source: """
|
||||
window.AndroidInterface = {
|
||||
getUserInfo: async function () {
|
||||
return window.flutter_inappwebview.callHandler('getUserInfo');
|
||||
},
|
||||
};
|
||||
""",
|
||||
window.AndroidInterface = {
|
||||
getUserInfo: async function () {
|
||||
return window.flutter_inappwebview.callHandler('getUserInfo');
|
||||
},
|
||||
};
|
||||
""",
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
// loadingStatus = LoadStatusType.loadSuccess;
|
||||
loadingStatus = KtLoadStatusType.loadSuccess;
|
||||
});
|
||||
_webRefreshController.endRefreshing();
|
||||
},
|
||||
@ -239,16 +221,68 @@ class SignInActivityPageState extends State<WalletPage> with RouteAware {
|
||||
_webRefreshController.endRefreshing();
|
||||
Future.delayed(const Duration(milliseconds: 100)).then((_) {
|
||||
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_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 {
|
||||
const KtStorePage({super.key});
|
||||
@ -8,10 +15,82 @@ class KtStorePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _KtStorePageState extends State<KtStorePage> {
|
||||
final logic = Get.put(KtStoreLogic());
|
||||
final state = Get.find<KtStoreLogic>().state;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Text('Store', style: TextStyle(color: Colors.black)),
|
||||
return Container(
|
||||
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_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_routes.dart';
|
||||
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||
@ -54,97 +55,157 @@ class _KtMinePageState extends State<KtMinePage> {
|
||||
onRefresh: logic.getUserInfo,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: ScreenUtil().screenWidth - 30.w,
|
||||
margin: EdgeInsets.only(top: 22.w),
|
||||
padding: EdgeInsets.fromLTRB(6.w, 13.w, 6.w, 6.w),
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('ic_mine_top_bg.png'.ktIcon),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Welcome Back, visitor!',
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
width: ScreenUtil().screenWidth - 30.w,
|
||||
margin: EdgeInsets.only(top: 10.w),
|
||||
padding: EdgeInsets.fromLTRB(6.w, 13.w, 6.w, 6.w),
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('ic_mine_top_bg.png'.ktIcon),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 17.w,
|
||||
horizontal: 13.w,
|
||||
),
|
||||
margin: EdgeInsets.only(top: 13.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(14.w),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'ic_avatar.png'.ktIcon,
|
||||
width: 56.w,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Coins',
|
||||
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.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),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
margin: EdgeInsets.only(top: 8.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(14.w),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
Image.asset(
|
||||
'ic_avatar.png'.ktIcon,
|
||||
width: 56.w,
|
||||
),
|
||||
SizedBox(width: 13.w),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
'text_bg_discover.png'.ktIcon,
|
||||
height: 17.w,
|
||||
width: 55.w,
|
||||
),
|
||||
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,
|
||||
Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Image.asset(
|
||||
'text_bg_discover.png'.ktIcon,
|
||||
height: 17.w,
|
||||
width: 55.w,
|
||||
),
|
||||
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(),
|
||||
],
|
||||
),
|
||||
@ -158,6 +219,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() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -185,6 +364,13 @@ class _KtMinePageState extends State<KtMinePage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
handleItem(
|
||||
'ic_order_record.png',
|
||||
'Order Record',
|
||||
() => Get.to(
|
||||
() => KtOrderRecordPage(
|
||||
),
|
||||
),
|
||||
), handleItem(
|
||||
'ic_privacy.png',
|
||||
'Privacy Policy',
|
||||
() => Get.to(
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_kinetra/kt_model/kt_short_video_bean.dart';
|
||||
import 'package:flutter_kinetra/kt_pages/kt_my_list/kt_my_chest_page.dart';
|
||||
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||
import 'package:flutter_kinetra/kt_widgets/kt_status_widget.dart';
|
||||
|
@ -1,8 +1,8 @@
|
||||
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 '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_store/view.dart';
|
||||
import 'kt_short_video/view.dart';
|
||||
@ -36,7 +36,7 @@ class KtRoutes {
|
||||
preventDuplicates: false,
|
||||
),
|
||||
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: helpCenter, page: () => const HelpCenterPage()),
|
||||
// GetPage(name: helpCenterList, page: () => const HelpCenterListPage()),
|
||||
|
@ -1,5 +1,11 @@
|
||||
import 'package:easy_debounce/easy_throttle.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_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:video_player/video_player.dart';
|
||||
|
||||
@ -7,15 +13,19 @@ import '../../dio_cilent/kt_apis.dart';
|
||||
import '../../dio_cilent/kt_request.dart';
|
||||
import '../../kt_model/kt_video_detail_bean.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 {
|
||||
final state = VideoPlayState();
|
||||
|
||||
late final PageController pageController;
|
||||
List<VideoPlayerController?> controllers = [];
|
||||
|
||||
KtLoadStatusType videoStatus = KtLoadStatusType.loading;
|
||||
KtLoadStatusType loadStatusType = KtLoadStatusType.loadSuccess;
|
||||
int currentIndex = 0;
|
||||
bool _disposed = false;
|
||||
final userLogic = Get.put(KtMineLogic());
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -60,7 +70,6 @@ class VideoPlayLogic extends GetxController {
|
||||
}
|
||||
clearCacheCtrl();
|
||||
try {
|
||||
// LottieLoading.show(Get.context!);
|
||||
ApiResponse res = await KtHttpClient().request(
|
||||
KtApis.getVideoDetails,
|
||||
method: HttpMethod.get,
|
||||
@ -77,21 +86,17 @@ class VideoPlayLogic extends GetxController {
|
||||
}
|
||||
state.episodeList = state.video?.episodeList ?? [];
|
||||
if (toPage) currentIndex = (state.video?.videoInfo?.episode ?? 1) - 1;
|
||||
// for (var video in state.episodeList) {
|
||||
// if (video.isLock == true) {
|
||||
// state.curUnlock = state.episodeList.indexOf(video);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// reportHistory();
|
||||
for (var video in state.episodeList) {
|
||||
if (video.isLock == true) {
|
||||
state.curUnlock = state.episodeList.indexOf(video);
|
||||
break;
|
||||
}
|
||||
}
|
||||
controllers = List<VideoPlayerController?>.filled(
|
||||
state.episodeList.length,
|
||||
null,
|
||||
growable: true,
|
||||
);
|
||||
// await _initializeController(currentIndex);
|
||||
// _preloadAdjacentVideos();
|
||||
// if(currentIndex != 0){
|
||||
if (toPage) {
|
||||
onPageChanged(
|
||||
currentIndex,
|
||||
@ -101,17 +106,15 @@ class VideoPlayLogic extends GetxController {
|
||||
} else {
|
||||
_initializeController(currentIndex);
|
||||
}
|
||||
// }
|
||||
|
||||
update();
|
||||
} else {
|
||||
state.loadStatus = KtLoadStatusType.loadFailed;
|
||||
update();
|
||||
}
|
||||
// if (userLogic.state.userInfo.isVip == true) state.curUnlock = 9999;
|
||||
if (userLogic.state.userInfo.isVip == true) state.curUnlock = 9999;
|
||||
update();
|
||||
} catch (e) {
|
||||
// LottieLoading.close();
|
||||
state.loadStatus = KtLoadStatusType.loadFailed;
|
||||
update();
|
||||
}
|
||||
@ -149,42 +152,49 @@ class VideoPlayLogic extends GetxController {
|
||||
// 暂停当前视频
|
||||
if (controllers[currentIndex]?.value.isPlaying ?? false) {
|
||||
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 (isUploadHistorySeconds) uploadHistorySeconds(0);
|
||||
if (isUploadHistorySeconds) uploadHistorySeconds(0);
|
||||
if (state.activityId != null) reportActivity();
|
||||
}
|
||||
if (isToggle) {
|
||||
// loadStatusType = LoadStatusType.loading;
|
||||
loadStatusType = KtLoadStatusType.loading;
|
||||
update();
|
||||
|
||||
await _initializeController(index);
|
||||
// loadStatusType = LoadStatusType.loadSuccess;
|
||||
loadStatusType = KtLoadStatusType.loadSuccess;
|
||||
update();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
pageController.jumpToPage(index);
|
||||
});
|
||||
}
|
||||
currentIndex = index;
|
||||
// if (state.episodeList[index].isLock == true) {
|
||||
// controllers[index]?.seekTo(Duration(seconds: 0));
|
||||
// controllers[index]?.pause();
|
||||
// update();
|
||||
// // showUnlockDialog();
|
||||
// //
|
||||
// return;
|
||||
// }
|
||||
if (state.episodeList[index].isLock == true) {
|
||||
controllers[index]?.seekTo(Duration(seconds: 0));
|
||||
controllers[index]?.pause();
|
||||
update();
|
||||
// EasyThrottle.throttle(
|
||||
// 'show-buy-dialog',
|
||||
// Duration(milliseconds: 3000),
|
||||
// () => showStoreDialog(state.episodeList[index].coins ?? 0),
|
||||
// );
|
||||
return;
|
||||
}
|
||||
|
||||
if (controllers[index] != null) {
|
||||
// if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
||||
controllers[index]?.play();
|
||||
// }
|
||||
if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
||||
controllers[index]?.play();
|
||||
}
|
||||
} else {
|
||||
if (!isToggle) await _initializeController(index);
|
||||
// if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
||||
controllers[index]?.play();
|
||||
// }
|
||||
if (state.curUnlock > index || userLogic.state.userInfo.isVip == true) {
|
||||
controllers[index]?.play();
|
||||
}
|
||||
}
|
||||
controllers[index]?.setPlaybackSpeed(state.currentSpeed);
|
||||
// updateHomeVideo();
|
||||
@ -221,8 +231,7 @@ class VideoPlayLogic extends GetxController {
|
||||
|
||||
try {
|
||||
await controller.initialize();
|
||||
// if (index == currentIndex && (episode.isLock == false || userLogic.state.userInfo.isVip == true)) {
|
||||
if (index == currentIndex) {
|
||||
if (index == currentIndex && (episode.isLock == false || userLogic.state.userInfo.isVip == true)) {
|
||||
controller.play();
|
||||
update();
|
||||
}
|
||||
@ -261,6 +270,15 @@ class VideoPlayLogic extends GetxController {
|
||||
_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 {
|
||||
if (state.video == null) return;
|
||||
|
||||
@ -276,18 +294,128 @@ class VideoPlayLogic extends GetxController {
|
||||
|
||||
state.video?.shortPlayInfo?.isCollect =
|
||||
!(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();
|
||||
}
|
||||
|
||||
// 购买剧集
|
||||
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 = '';
|
||||
int shortPlayId = -1;
|
||||
num videoId = -1;
|
||||
int curUnlock = 999;
|
||||
int? activityId;
|
||||
bool isFromDiscover = false;
|
||||
VideoDetailBean? video;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||
import 'package:flutter_kinetra/kt_utils/kt_utils.dart';
|
||||
@ -8,6 +9,7 @@ import 'package:get/get.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
import '../../kt_utils/kt_toast_utils.dart';
|
||||
import '../../kt_widgets/kt_network_image.dart';
|
||||
import '../../kt_widgets/kt_status_widget.dart';
|
||||
import '../../kt_widgets/kt_video_progress_bar.dart';
|
||||
@ -128,15 +130,14 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
||||
if (logic.controllers.isEmpty) return Container();
|
||||
|
||||
final controller = logic.controllers[index];
|
||||
// final episode = state.episodeList[index];
|
||||
final episode = state.episodeList[index];
|
||||
return Stack(
|
||||
children: [
|
||||
// 视频播放器
|
||||
if (controller != null && controller.value.isInitialized && !isAllOver)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// if (episode.isLock == true) return;
|
||||
|
||||
if (episode.isLock == true) return;
|
||||
controller.value.isPlaying
|
||||
? controller.pause()
|
||||
: controller.play();
|
||||
@ -153,7 +154,7 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
||||
onVisibilityChanged: (VisibilityInfo info) {
|
||||
var visiblePercentage = info.visibleFraction * 100;
|
||||
if (visiblePercentage > 20) {
|
||||
// if (episode.isLock == true) return;
|
||||
if (episode.isLock == true) return;
|
||||
controller.play();
|
||||
} else {
|
||||
controller.pause();
|
||||
@ -201,48 +202,89 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
||||
],
|
||||
),
|
||||
|
||||
// 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,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
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),
|
||||
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 顶部返回
|
||||
Positioned(
|
||||
top: ScreenUtil().statusBarHeight + 10.w,
|
||||
@ -623,11 +665,13 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Get.back();
|
||||
// bool? beforeEpUnlock = index - 2 <= 0 ? false : state.episodeList[index - 2].isLock;
|
||||
// if (beforeEpUnlock ?? false) {
|
||||
// ToastUtils.show('Unable to unlock episodes');
|
||||
// return;
|
||||
// }
|
||||
bool? beforeEpUnlock = index - 1 <= 0
|
||||
? false
|
||||
: state.episodeList[index - 1].isLock;
|
||||
if (beforeEpUnlock ?? false) {
|
||||
KtToastUtils.showError('Unable to unlock episodes');
|
||||
return;
|
||||
}
|
||||
logic.onPageChanged(index, isToggle: true);
|
||||
},
|
||||
child: Stack(
|
||||
@ -654,27 +698,15 @@ class _VideoPlayPageState extends State<VideoPlayPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (state.episodeList[episode - 1].isLock == true)
|
||||
// Positioned(
|
||||
// right: 0,
|
||||
// top: 0,
|
||||
// child: Container(
|
||||
// width: 20.w,
|
||||
// height: 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,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
if (state.episodeList[index].isLock == true)
|
||||
Positioned(
|
||||
right: 5.w,
|
||||
top: 5.w,
|
||||
child: Image.asset(
|
||||
'ic_lock.png'.ktIcon,
|
||||
width: 12.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) {
|
||||
return showToast(
|
||||
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_utils/kt_device_info_utils.dart';
|
||||
import 'kt_utils/kt_keys.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|