feat:会员和个人中心
BIN
assets/ic_apple.png
Normal file
|
After Width: | Height: | Size: 868 B |
BIN
assets/ic_fb.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/login_bg.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
assets/login_sheet_bg.png
Normal file
|
After Width: | Height: | Size: 291 KiB |
BIN
assets/month_text_bg.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/quarter_text_bg.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/year_text_bg.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
@ -81,6 +81,9 @@ class KtGoodsBean {
|
|||||||
int? payTemplateId,
|
int? payTemplateId,
|
||||||
String? deletedAt,
|
String? deletedAt,
|
||||||
int? payProductId,
|
int? payProductId,
|
||||||
|
int? isFirstBuy,
|
||||||
|
int? isDiscount,
|
||||||
|
int? discountType,
|
||||||
ExtInfo? extInfo,
|
ExtInfo? extInfo,
|
||||||
String? factor,
|
String? factor,
|
||||||
String? shortType,
|
String? shortType,
|
||||||
@ -125,6 +128,9 @@ class KtGoodsBean {
|
|||||||
_payTemplateId = payTemplateId;
|
_payTemplateId = payTemplateId;
|
||||||
_deletedAt = deletedAt;
|
_deletedAt = deletedAt;
|
||||||
_payProductId = payProductId;
|
_payProductId = payProductId;
|
||||||
|
_discountType = discountType;
|
||||||
|
_isDiscount = isDiscount;
|
||||||
|
_isFirstBuy = isFirstBuy;
|
||||||
_extInfo = extInfo;
|
_extInfo = extInfo;
|
||||||
_factor = factor;
|
_factor = factor;
|
||||||
_shortType = shortType;
|
_shortType = shortType;
|
||||||
@ -171,6 +177,9 @@ class KtGoodsBean {
|
|||||||
_payTemplateId = json['pay_template_id'];
|
_payTemplateId = json['pay_template_id'];
|
||||||
_deletedAt = json['deleted_at'];
|
_deletedAt = json['deleted_at'];
|
||||||
_payProductId = json['pay_product_id'];
|
_payProductId = json['pay_product_id'];
|
||||||
|
_discountType = json['discount_type'];
|
||||||
|
_isDiscount = json['is_discount'];
|
||||||
|
_isFirstBuy = json['is_first_buy'];
|
||||||
_extInfo = json['ext_info'] != null ? ExtInfo.fromJson(json['ext_info']) : null;
|
_extInfo = json['ext_info'] != null ? ExtInfo.fromJson(json['ext_info']) : null;
|
||||||
_factor = json['factor'];
|
_factor = json['factor'];
|
||||||
_shortType = json['short_type'];
|
_shortType = json['short_type'];
|
||||||
@ -216,6 +225,9 @@ class KtGoodsBean {
|
|||||||
int? _payTemplateId;
|
int? _payTemplateId;
|
||||||
String? _deletedAt;
|
String? _deletedAt;
|
||||||
int? _payProductId;
|
int? _payProductId;
|
||||||
|
int? _discountType;
|
||||||
|
int? _isDiscount;
|
||||||
|
int? _isFirstBuy;
|
||||||
ExtInfo? _extInfo;
|
ExtInfo? _extInfo;
|
||||||
String? _factor;
|
String? _factor;
|
||||||
String? _shortType;
|
String? _shortType;
|
||||||
@ -261,6 +273,9 @@ class KtGoodsBean {
|
|||||||
int? payTemplateId,
|
int? payTemplateId,
|
||||||
String? deletedAt,
|
String? deletedAt,
|
||||||
int? payProductId,
|
int? payProductId,
|
||||||
|
int? discountType,
|
||||||
|
int? isDiscount,
|
||||||
|
int? isFirstBuy,
|
||||||
ExtInfo? extInfo,
|
ExtInfo? extInfo,
|
||||||
String? factor,
|
String? factor,
|
||||||
String? shortType,
|
String? shortType,
|
||||||
@ -305,6 +320,9 @@ class KtGoodsBean {
|
|||||||
payTemplateId: payTemplateId ?? _payTemplateId,
|
payTemplateId: payTemplateId ?? _payTemplateId,
|
||||||
deletedAt: deletedAt ?? _deletedAt,
|
deletedAt: deletedAt ?? _deletedAt,
|
||||||
payProductId: payProductId ?? _payProductId,
|
payProductId: payProductId ?? _payProductId,
|
||||||
|
discountType: discountType ?? _discountType,
|
||||||
|
isDiscount: isDiscount ?? _isDiscount,
|
||||||
|
isFirstBuy: isFirstBuy ?? _isFirstBuy,
|
||||||
extInfo: extInfo ?? _extInfo,
|
extInfo: extInfo ?? _extInfo,
|
||||||
factor: factor ?? _factor,
|
factor: factor ?? _factor,
|
||||||
shortType: shortType ?? _shortType,
|
shortType: shortType ?? _shortType,
|
||||||
@ -383,6 +401,12 @@ class KtGoodsBean {
|
|||||||
|
|
||||||
int? get payProductId => _payProductId;
|
int? get payProductId => _payProductId;
|
||||||
|
|
||||||
|
int? get discountType => _discountType;
|
||||||
|
|
||||||
|
int? get isDiscount => _isDiscount;
|
||||||
|
|
||||||
|
int? get isFirstBuy => _isFirstBuy;
|
||||||
|
|
||||||
ExtInfo? get extInfo => _extInfo;
|
ExtInfo? get extInfo => _extInfo;
|
||||||
|
|
||||||
String? get factor => _factor;
|
String? get factor => _factor;
|
||||||
@ -470,6 +494,12 @@ class KtGoodsBean {
|
|||||||
|
|
||||||
set payProductId(int? value) => _payProductId = value;
|
set payProductId(int? value) => _payProductId = value;
|
||||||
|
|
||||||
|
set discountType(int? value) => _discountType = value;
|
||||||
|
|
||||||
|
set isDiscount(int? value) => _isDiscount = value;
|
||||||
|
|
||||||
|
set isFirstBuy(int? value) => _isFirstBuy = value;
|
||||||
|
|
||||||
set extInfo(ExtInfo? value) => _extInfo = value;
|
set extInfo(ExtInfo? value) => _extInfo = value;
|
||||||
|
|
||||||
set factor(String? value) => _factor = value;
|
set factor(String? value) => _factor = value;
|
||||||
@ -525,6 +555,9 @@ class KtGoodsBean {
|
|||||||
map['pay_template_id'] = _payTemplateId;
|
map['pay_template_id'] = _payTemplateId;
|
||||||
map['deleted_at'] = _deletedAt;
|
map['deleted_at'] = _deletedAt;
|
||||||
map['pay_product_id'] = _payProductId;
|
map['pay_product_id'] = _payProductId;
|
||||||
|
map['discount_type'] = _discountType;
|
||||||
|
map['is_discount'] = _isDiscount;
|
||||||
|
map['is_first_buy'] = _isFirstBuy;
|
||||||
if (_extInfo != null) {
|
if (_extInfo != null) {
|
||||||
map['ext_info'] = _extInfo?.toJson();
|
map['ext_info'] = _extInfo?.toJson();
|
||||||
}
|
}
|
||||||
@ -705,5 +738,4 @@ class BackhaulPercentConf {
|
|||||||
map['backhaul_percent'] = _backhaulPercent;
|
map['backhaul_percent'] = _backhaulPercent;
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
32
lib/kt_model/kt_login_bean.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
/// customer_id : ""
|
||||||
|
/// token : ""
|
||||||
|
|
||||||
|
KtLoginBean ktLoginBeanFromJson(String str) => KtLoginBean.fromJson(json.decode(str));
|
||||||
|
String ktLoginBeanToJson(KtLoginBean data) => json.encode(data.toJson());
|
||||||
|
|
||||||
|
class KtLoginBean {
|
||||||
|
KtLoginBean({String? customerId, String? token}) {
|
||||||
|
_customerId = customerId;
|
||||||
|
_token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
KtLoginBean.fromJson(dynamic json) {
|
||||||
|
_customerId = json['customer_id'];
|
||||||
|
_token = json['token'];
|
||||||
|
}
|
||||||
|
String? _customerId;
|
||||||
|
String? _token;
|
||||||
|
KtLoginBean copyWith({String? customerId, String? token}) =>
|
||||||
|
KtLoginBean(customerId: customerId ?? _customerId, token: token ?? _token);
|
||||||
|
String? get customerId => _customerId;
|
||||||
|
String? get token => _token;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final map = <String, dynamic>{};
|
||||||
|
map['customer_id'] = _customerId;
|
||||||
|
map['token'] = _token;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -69,7 +69,7 @@ class KtStoreLogic extends GetxController {
|
|||||||
if (res.success) {
|
if (res.success) {
|
||||||
state.storeBean = KtStoreBean.fromJson(res.data);
|
state.storeBean = KtStoreBean.fromJson(res.data);
|
||||||
update();
|
update();
|
||||||
initStore();
|
// initStore();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
EasyLoading.dismiss();
|
EasyLoading.dismiss();
|
||||||
|
|||||||
@ -1,13 +1,25 @@
|
|||||||
import 'package:flustars/flustars.dart';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flustars/flustars.dart' hide ScreenUtil;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
|
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
|
||||||
import 'package:flutter_kinetra/dio_cilent/kt_apis.dart';
|
import 'package:flutter_kinetra/dio_cilent/kt_apis.dart';
|
||||||
import 'package:flutter_kinetra/kt_pages/kt_mine/state.dart';
|
import 'package:flutter_kinetra/kt_pages/kt_mine/state.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||||
import 'package:flutter_kinetra/kt_utils/kt_utils.dart';
|
import 'package:flutter_kinetra/kt_utils/kt_utils.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
||||||
|
|
||||||
import '../../dio_cilent/kt_request.dart';
|
import '../../dio_cilent/kt_request.dart';
|
||||||
|
import '../../kt_model/kt_login_bean.dart';
|
||||||
import '../../kt_model/kt_user_info.dart';
|
import '../../kt_model/kt_user_info.dart';
|
||||||
import '../../kt_utils/kt_keys.dart';
|
import '../../kt_utils/kt_keys.dart';
|
||||||
|
import '../../kt_utils/kt_toast_utils.dart';
|
||||||
|
import '../../kt_widgets/kt_dialog.dart';
|
||||||
|
import '../kt_routes.dart';
|
||||||
|
import '../kt_webview_page.dart';
|
||||||
|
|
||||||
class KtMineLogic extends GetxController {
|
class KtMineLogic extends GetxController {
|
||||||
final state = KtMineState();
|
final state = KtMineState();
|
||||||
@ -39,4 +51,237 @@ class KtMineLogic extends GetxController {
|
|||||||
refreshController.refreshCompleted();
|
refreshController.refreshCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showLoginDialog() {
|
||||||
|
Get.bottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
Container(
|
||||||
|
height: 510.w,
|
||||||
|
width: ScreenUtil().screenWidth,
|
||||||
|
padding: EdgeInsets.fromLTRB(0.w, 68.w, 0.w, 20.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
// borderRadius: BorderRadius.vertical(top: Radius.circular(12.w)),
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('login_sheet_bg.png'.ktIcon),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Image.asset('login_bg.png'.ktIcon, width: 322.w),
|
||||||
|
SizedBox(height: 14.w),
|
||||||
|
Text(
|
||||||
|
'Welcome to LimeTale',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
color: Color(0xFF1C1C1C),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: loginWithFacebook,
|
||||||
|
child: Container(
|
||||||
|
width: ScreenUtil().screenWidth - 30.w,
|
||||||
|
padding: EdgeInsets.fromLTRB(35.w, 13.w, 30.w, 13.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(width: 1.w, color: Color(0xFF1E1E20)),
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Image.asset('ic_fb.png'.ktIcon, width: 20.w),
|
||||||
|
Text(
|
||||||
|
'Login with Facebook',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Color(0xFF1C1C1C),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Image.asset('ic_right_arrow.png'.ktIcon, width: 14.w),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (Platform.isIOS)
|
||||||
|
GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
width: ScreenUtil().screenWidth - 30.w,
|
||||||
|
margin: EdgeInsets.only(top: 20.w),
|
||||||
|
padding: EdgeInsets.fromLTRB(35.w, 13.w, 30.w, 13.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Color(0xFF1C1C1C),
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Image.asset('ic_apple.png'.ktIcon, width: 20.w),
|
||||||
|
Text(
|
||||||
|
'Login with Facebook',
|
||||||
|
style: TextStyle(fontSize: 14.sp, color: Colors.white),
|
||||||
|
),
|
||||||
|
Image.asset('ic_right_white.png'.ktIcon, width: 14.w),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'By logging in you agree to:',
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: Color(0xFF777777)),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Get.to(
|
||||||
|
() => KtWebViewPage(
|
||||||
|
url: KtApis.WEB_SITE_POLICY,
|
||||||
|
title: "User Agreement",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'User Agreement',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
decoration: TextDecoration.underline, // 添加下划线
|
||||||
|
color: Color(0xFF777777),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
' & ',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: Color(0xFF777777),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Get.to(
|
||||||
|
() => KtWebViewPage(
|
||||||
|
url: KtApis.WEB_SITE_PRIVATE,
|
||||||
|
title: "Privacy Policy",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Privacy Policy',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
decoration: TextDecoration.underline, // 添加下划线
|
||||||
|
color: Color(0xFF777777),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loginWithFacebook() async {
|
||||||
|
final LoginResult result = await FacebookAuth.instance.login(
|
||||||
|
permissions: ['email', 'public_profile'],
|
||||||
|
loginBehavior: LoginBehavior.nativeWithFallback,
|
||||||
|
);
|
||||||
|
if (result.status == LoginStatus.success) {
|
||||||
|
try {
|
||||||
|
EasyLoading.show(status: '', dismissOnTap: true);
|
||||||
|
final userData = await FacebookAuth.instance.getUserData();
|
||||||
|
final String facebookAvatar =
|
||||||
|
userData['picture']?['data']?['url'] ?? "";
|
||||||
|
final String facebookId = userData['id'] ?? "";
|
||||||
|
final String facebookEmail = userData['email'] ?? "";
|
||||||
|
final String facebookName = userData['name'] ?? "";
|
||||||
|
loginRequest(
|
||||||
|
facebookAvatar,
|
||||||
|
facebookEmail,
|
||||||
|
facebookName,
|
||||||
|
'facebook',
|
||||||
|
facebookId,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
KtToastUtils.showError('Login failed');
|
||||||
|
debugPrint('---facebook failed-:${result.status} ${result.message}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loginRequest(String avatar, email, familyName, platform, thirdId) async {
|
||||||
|
Map<String, dynamic> params = {
|
||||||
|
"avator": avatar,
|
||||||
|
"email": email,
|
||||||
|
"family_name": familyName,
|
||||||
|
"platform": platform,
|
||||||
|
"third_id": thirdId,
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiResponse res = await KtHttpClient().request(KtApis.login, data: params);
|
||||||
|
EasyLoading.dismiss();
|
||||||
|
if (res.success) {
|
||||||
|
debugPrint('----old token"${SpUtil.getString(KtKeys.token)}');
|
||||||
|
String? token = SpUtil.getString(KtKeys.token);
|
||||||
|
await KtHttpClient().request(
|
||||||
|
KtApis.leaveApp,
|
||||||
|
data: {'PostAuthorization': token ?? ''},
|
||||||
|
);
|
||||||
|
|
||||||
|
await KtHttpClient().request(
|
||||||
|
KtApis.onLine,
|
||||||
|
data: {'PostAuthorization': token ?? ''},
|
||||||
|
);
|
||||||
|
KtLoginBean data = KtLoginBean.fromJson(res.data);
|
||||||
|
|
||||||
|
SpUtil.putString(KtKeys.token, data.token ?? '');
|
||||||
|
KtHttpClient().setAuthToken(data.token ?? '');
|
||||||
|
debugPrint('----new token"${SpUtil.getString(KtKeys.token)}');
|
||||||
|
await KtHttpClient().request(
|
||||||
|
KtApis.enterTheApp,
|
||||||
|
data: {'is_open_notice': 0},
|
||||||
|
);
|
||||||
|
getUserInfo();
|
||||||
|
|
||||||
|
// Get.offAllNamed(KtRoutes.home);
|
||||||
|
} else {
|
||||||
|
KtToastUtils.showError('Login Failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSignOut() {
|
||||||
|
Get.dialog(
|
||||||
|
KtDialog(
|
||||||
|
topIcon: 'dialog_logout.png',
|
||||||
|
title: 'Log out?',
|
||||||
|
subTitle: 'Sign in again to access favorites',
|
||||||
|
leftBtnText: 'Log Out',
|
||||||
|
leftBtnFunc: signOut,
|
||||||
|
rightBtnText: 'Stay',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
signOut() async {
|
||||||
|
String? token = SpUtil.getString(KtKeys.token);
|
||||||
|
await KtHttpClient().request(
|
||||||
|
KtApis.leaveApp,
|
||||||
|
data: {'PostAuthorization': token ?? ''},
|
||||||
|
);
|
||||||
|
ApiResponse res = await KtHttpClient().request(KtApis.signOut);
|
||||||
|
if (res.success) {
|
||||||
|
KtToastUtils.showSuccess(placeholder: 'Logout Success');
|
||||||
|
|
||||||
|
// await UserUtil().register(toHome: false);
|
||||||
|
getUserInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -198,6 +198,38 @@ class _KtMinePageState extends State<KtMinePage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
logic.isLogin
|
||||||
|
? logic.checkSignOut()
|
||||||
|
: logic.showLoginDialog();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 78.w,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
vertical: 11.w,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(100),
|
||||||
|
border: Border.all(
|
||||||
|
width: 1.w,
|
||||||
|
color: Color(0xFFC0C0C0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
logic.isLogin
|
||||||
|
? 'Log out'
|
||||||
|
: 'Log in',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: Color(0xFFC0C0C0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
||||||
|
import 'package:in_app_purchase_android/billing_client_wrappers.dart';
|
||||||
|
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
|
||||||
|
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
|
||||||
|
|
||||||
class KtUtils {
|
class KtUtils {
|
||||||
static String getTimeZoneOffset(DateTime dateTime) {
|
static String getTimeZoneOffset(DateTime dateTime) {
|
||||||
Duration offset = dateTime.timeZoneOffset;
|
Duration offset = dateTime.timeZoneOffset;
|
||||||
@ -18,4 +26,60 @@ class KtUtils {
|
|||||||
static bool isNotEmpty(dynamic value) {
|
static bool isNotEmpty(dynamic value) {
|
||||||
return !isEmpty(value);
|
return !isEmpty(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static String getDiscountPrice(ProductDetails? product, {bool showDiscount = false}) {
|
||||||
|
String price = product?.price ?? '';
|
||||||
|
if (product == null) return price;
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
if (product is AppStoreProductDetails) {
|
||||||
|
debugPrint("skProduct优惠价格: ${product.skProduct.price}");
|
||||||
|
for (final discount in product.skProduct.discounts) {
|
||||||
|
debugPrint("优惠ID: ${discount.identifier}");
|
||||||
|
debugPrint("优惠价格: ${discount.price}");
|
||||||
|
debugPrint("priceLocale: ${discount.priceLocale.currencySymbol}-${discount.priceLocale.currencyCode}");
|
||||||
|
debugPrint("优惠周期: ${discount.subscriptionPeriod.numberOfUnits}");
|
||||||
|
debugPrint("支付模式: ${discount.paymentMode}"); // freeTrial, payAsYouGo, payUpFront
|
||||||
|
if (isNotEmpty(discount.price)) price = discount.priceLocale.currencySymbol + discount.price;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
if (product is GooglePlayProductDetails) {
|
||||||
|
for (SubscriptionOfferDetailsWrapper? offer in product.productDetails.subscriptionOfferDetails ?? []) {
|
||||||
|
debugPrint('Base plan ID: ${offer?.basePlanId}');
|
||||||
|
debugPrint('Offer ID: ${offer?.offerId}');
|
||||||
|
if (offer?.pricingPhases.isEmpty ?? true) continue;
|
||||||
|
for (PricingPhaseWrapper phase in offer!.pricingPhases) {
|
||||||
|
debugPrint('价格: ${phase.formattedPrice}');
|
||||||
|
debugPrint('周期: ${phase.billingPeriod}');
|
||||||
|
debugPrint('周期内收费次数: ${phase.recurrenceMode}');
|
||||||
|
if (isNotEmpty(phase.formattedPrice)) {
|
||||||
|
price = phase.formattedPrice;
|
||||||
|
if (showDiscount) return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> filterVipType(String? type) {
|
||||||
|
if (isEmpty(type)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
List<Map<String, dynamic>> vipTypes = [
|
||||||
|
{'type': 'week', 'name': 'Weekly', 'shortName': 'week'},
|
||||||
|
{'type': 'month', 'name': 'Monthly', 'shortName': 'month'},
|
||||||
|
{'type': 'three_months', 'name': 'Quarterly', 'shortName': 'quarter'},
|
||||||
|
{'type': 'yearly', 'name': 'Yearly', 'shortName': 'year'},
|
||||||
|
{'type': 'year', 'name': 'Yearly', 'shortName': 'year'},
|
||||||
|
];
|
||||||
|
final filterType = vipTypes.where((item) => item['type'] == type).cast<Map<String, dynamic>?>().firstOrNull;
|
||||||
|
if (filterType != null) {
|
||||||
|
return filterType;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ class KtDialog extends StatelessWidget {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 15.sp,
|
fontSize: 15.sp,
|
||||||
color: Color(0xFF1C1C1C),
|
color: Color(0xFF1C1C1C),
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_kinetra/kt_model/kt_goods_bean.dart';
|
import 'package:flutter_kinetra/kt_model/kt_goods_bean.dart';
|
||||||
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
import 'package:flutter_kinetra/kt_utils/kt_string_extend.dart';
|
||||||
|
import 'package:flutter_kinetra/kt_utils/kt_utils.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
|
||||||
import '../kt_model/kt_store_bean.dart';
|
import '../kt_model/kt_store_bean.dart';
|
||||||
@ -35,13 +38,16 @@ class _KtStoreWidgetState extends State<KtStoreWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget descView() {
|
Widget descView() {
|
||||||
return Text('''
|
return Text(
|
||||||
|
'''
|
||||||
1. Coins are virtual items and cannot be refunded. Use it for this product. \n
|
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
|
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
|
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
|
4. The purchase has not been credited, click <Restore> to refresh. \n
|
||||||
5. For other questions, contact us via Profile>Help &feedback.\n
|
5. For other questions, contact us via Profile>Help &feedback.\n
|
||||||
''', style: TextStyle(fontSize: 12.sp, color: Color(0xFF848484),height: 0.95));
|
''',
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: Color(0xFF848484), height: 0.95),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> initWidget() {
|
List<Widget> initWidget() {
|
||||||
@ -51,11 +57,10 @@ class _KtStoreWidgetState extends State<KtStoreWidget> {
|
|||||||
// int coinsIndex = widget.store.sort?.indexOf('list_coins') ?? 0;
|
// int coinsIndex = widget.store.sort?.indexOf('list_coins') ?? 0;
|
||||||
// int vipIndex = widget.store.sort?.indexOf('list_sub_vip') ?? 0;
|
// int vipIndex = widget.store.sort?.indexOf('list_sub_vip') ?? 0;
|
||||||
// if (coinsIndex < vipIndex) {
|
// if (coinsIndex < vipIndex) {
|
||||||
// widgets.addAll([coinList(), SizedBox(height: 15.w), vipList()]);
|
widgets.addAll([coinList(), SizedBox(height: 15.w), vipList()]);
|
||||||
// } else {
|
// } else {
|
||||||
// widgets.addAll([vipList(), SizedBox(height: 15.w), coinList()]);
|
// widgets.addAll([vipList(), SizedBox(height: 15.w), coinList()]);
|
||||||
// }
|
// }
|
||||||
widgets.addAll([coinList()]);
|
|
||||||
|
|
||||||
widgets.addAll([
|
widgets.addAll([
|
||||||
if (widget.store.payMode == 0 && widget.store.showType == 0)
|
if (widget.store.payMode == 0 && widget.store.showType == 0)
|
||||||
@ -75,7 +80,7 @@ class _KtStoreWidgetState extends State<KtStoreWidget> {
|
|||||||
Widget coinList() {
|
Widget coinList() {
|
||||||
if (widget.store.listCoins?.isEmpty ?? true) return Container();
|
if (widget.store.listCoins?.isEmpty ?? true) return Container();
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (!widget.onlyCoins)
|
if (!widget.onlyCoins)
|
||||||
@ -85,7 +90,9 @@ class _KtStoreWidgetState extends State<KtStoreWidget> {
|
|||||||
'Go Coins',
|
'Go Coins',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color:widget.isStoreDialog? Color(0xFF1E1E20):Colors.white,
|
color: widget.isStoreDialog
|
||||||
|
? Color(0xFF1E1E20)
|
||||||
|
: Colors.white,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -93,7 +100,9 @@ class _KtStoreWidgetState extends State<KtStoreWidget> {
|
|||||||
' | Limited-time coin packs',
|
' | Limited-time coin packs',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color:widget.isStoreDialog? Color(0xFF1E1E20):Colors.white,
|
color: widget.isStoreDialog
|
||||||
|
? Color(0xFF1E1E20)
|
||||||
|
: Colors.white,
|
||||||
fontWeight: FontWeight.w200,
|
fontWeight: FontWeight.w200,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -129,10 +138,7 @@ class _KtStoreWidgetState extends State<KtStoreWidget> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
Image.asset('ic_coin.png'.ktIcon, width: 22.4.w),
|
||||||
'ic_coin.png'.ktIcon,
|
|
||||||
width: 22.4.w,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
'${item.coins ?? 0}',
|
'${item.coins ?? 0}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -179,10 +185,7 @@ class _KtStoreWidgetState extends State<KtStoreWidget> {
|
|||||||
Positioned(
|
Positioned(
|
||||||
top: 4.w,
|
top: 4.w,
|
||||||
left: 4.w,
|
left: 4.w,
|
||||||
child: Image.asset(
|
child: Image.asset('ic_fire.png'.ktIcon, width: 16.w),
|
||||||
'ic_fire.png'.ktIcon,
|
|
||||||
width: 16.w,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if ((item.sendCoins ?? 0) > 0)
|
if ((item.sendCoins ?? 0) > 0)
|
||||||
Positioned(
|
Positioned(
|
||||||
@ -361,4 +364,186 @@ class _KtStoreWidgetState extends State<KtStoreWidget> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget vipList() {
|
||||||
|
if (widget.store.listSubVip?.isEmpty ?? true) return Container();
|
||||||
|
if (widget.store.payMode == 1) return Container();
|
||||||
|
final colors = {
|
||||||
|
"month": Color(0xFF00679A),
|
||||||
|
"week": Color(0xFF3D6D00),
|
||||||
|
"quarter": Color(0xFF5C5FB5),
|
||||||
|
"year": Color(0xFFAE6F00),
|
||||||
|
};
|
||||||
|
final textColors = {
|
||||||
|
"month": Color(0xFFBDEBFF),
|
||||||
|
"week": Color(0xFFE7FCCA),
|
||||||
|
"quarter": Color(0xFFDBDDFF),
|
||||||
|
"year": Color(0xFFFFF2DA),
|
||||||
|
};
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Go VIP Premium | Auto renew, cancel anytime ',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.w),
|
||||||
|
ListView.separated(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
KtGoodsBean item = widget.store.listSubVip![index];
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
selVip = item.id ?? -1;
|
||||||
|
setState(() {});
|
||||||
|
widget.onItemTap?.call(item); // 回调
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: ScreenUtil().screenWidth - 30.w,
|
||||||
|
padding: EdgeInsets.fromLTRB(12.w, 19.w, 12.w, 5.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('vip_${item.vipTypeKey}_bg.png'.ktIcon),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
border: selVip == item.id
|
||||||
|
? Border.all(color: Colors.red, width: 1.5.w)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${item.shortType ?? ''} VIP',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.sp,
|
||||||
|
color: Color(0xFF2A0428),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
item.discountType != 0
|
||||||
|
? RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: Platform.isAndroid
|
||||||
|
? item.productDetails?.price
|
||||||
|
: KtUtils.getDiscountPrice(
|
||||||
|
item.productDetails,
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.sp,
|
||||||
|
color: Color(0xFF2A0428),
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: Platform.isIOS
|
||||||
|
? item.productDetails?.price
|
||||||
|
: KtUtils.getDiscountPrice(
|
||||||
|
item.productDetails,
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.sp,
|
||||||
|
color: Color(0xFFC2C2C2),
|
||||||
|
decoration:
|
||||||
|
TextDecoration.lineThrough, // 中间横线
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
// TextSpan(
|
||||||
|
// text: item.productDetails?.currencySymbol ?? '',
|
||||||
|
// style: TextStyle(fontSize: 16.sp, color: ColorResource.mainBlack),
|
||||||
|
// ),
|
||||||
|
TextSpan(
|
||||||
|
text: Platform.isIOS
|
||||||
|
? item.productDetails?.price
|
||||||
|
: KtUtils.getDiscountPrice(
|
||||||
|
item.productDetails,
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.sp,
|
||||||
|
color: Color(0xFF2A0428),
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
'/${KtUtils.filterVipType(item.vipType ?? '')['shortName']}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.sp,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.w),
|
||||||
|
|
||||||
|
SizedBox(height: 2.w),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Unlimited access to all series',
|
||||||
|
// item.description ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp,
|
||||||
|
color: Color(0xFF99889B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if ((item.sendCoins ?? 0) > 0)
|
||||||
|
Stack(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(top: 8.w),
|
||||||
|
child: Image.asset(
|
||||||
|
'${KtUtils.filterVipType(item.vipType ?? '')['shortName']}_text_bg.png'.ktIcon,
|
||||||
|
height: 10.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'+Extra ${item.sendCoins}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12.sp,
|
||||||
|
color: Color(0xFF1E1E20),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 2.w),
|
||||||
|
Image.asset(
|
||||||
|
'ic_coin.png'.ktIcon,
|
||||||
|
width: 15.w,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (_, __) => SizedBox(height: 10.w),
|
||||||
|
itemCount: widget.store.listSubVip?.length ?? 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||