提交 883b112b authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 f3019c68
{
"dart.flutterSdkPath": "~/flutter"
}
PODS:
- audio_session (0.0.1):
- Flutter
- Bugly (2.6.1)
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
......@@ -9,6 +10,9 @@ PODS:
- Flutter (1.0.0)
- flutter_app_update (0.0.1):
- Flutter
- flutter_bugly (0.0.1):
- Bugly
- Flutter
- flutter_inapp_purchase (0.0.1):
- Flutter
- flutter_inappwebview_ios (0.0.1):
......@@ -47,13 +51,6 @@ PODS:
- Flutter
- ScreenProtectorKit (~> 1.3.1)
- ScreenProtectorKit (1.3.1)
- Sentry/HybridSDK (8.21.0):
- SentryPrivate (= 8.21.0)
- sentry_flutter (7.19.0):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.21.0)
- SentryPrivate (8.21.0)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
......@@ -73,6 +70,7 @@ DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
- flutter_bugly (from `.symlinks/plugins/flutter_bugly/ios`)
- flutter_inapp_purchase (from `.symlinks/plugins/flutter_inapp_purchase/ios`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_sound (from `.symlinks/plugins/flutter_sound/ios`)
......@@ -84,19 +82,17 @@ DEPENDENCIES:
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- screen_protector (from `.symlinks/plugins/screen_protector/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- tobias (from `.symlinks/plugins/tobias/ios`)
SPEC REPOS:
trunk:
- Bugly
- flutter_sound_core
- OrderedSet
- ReachabilitySwift
- ScreenProtectorKit
- Sentry
- SentryPrivate
- WechatOpenSDK-XCFramework
EXTERNAL SOURCES:
......@@ -110,6 +106,8 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_app_update:
:path: ".symlinks/plugins/flutter_app_update/ios"
flutter_bugly:
:path: ".symlinks/plugins/flutter_bugly/ios"
flutter_inapp_purchase:
:path: ".symlinks/plugins/flutter_inapp_purchase/ios"
flutter_inappwebview_ios:
......@@ -132,8 +130,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/permission_handler_apple/ios"
screen_protector:
:path: ".symlinks/plugins/screen_protector/ios"
sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite:
......@@ -142,34 +138,33 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/tobias/ios"
SPEC CHECKSUMS:
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
audio_session: 9bdd3bf46960d4322cb8c3cb6138295dcfe84eee
Bugly: 217ac2ce5f0f2626d43dbaa4f70764c953a26a31
connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf
device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
flutter_inapp_purchase: 5c6a1ac3f11b11d0c8c0321c0c41c1f05805e4c8
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
flutter_sound: c60effa2a350fb977885f0db2fbc4c1ad5160900
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
flutter_bugly: 12197049262a692eab0bd2834d465a5647d920c2
flutter_inapp_purchase: 722da12971a50f306f37e62fc1aaf576b1cbecf6
flutter_inappwebview_ios: 25b61a1b550d1068e4ddaf490fc1d03c2ce6828d
flutter_sound: 49be32081884d275fe91d48262f4b1fcd86e10d3
flutter_sound_core: 26c10e5832e76aaacfae252d8925232281c486ae
flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d
fluwx: 3c7b6df42f83d444d4538f3eaeae079f12d30c37
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
flutter_tts: b88dbc8655d3dc961bc4a796e4e16a4cc1795833
fluwx: 70e62e0330c46b988a1c79ce9731f0d4bc4b26fb
image_picker_ios: 85f2b3c9fb98c09d63725c4d12ebd585b56ec35d
just_audio: 6c031bb61297cf218b4462be616638e81c058e97
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
package_info_plus: 566e1b7a2f3900e4b0020914ad3fc051dcc95596
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
permission_handler_apple: 3787117e48f80715ff04a3830ca039283d6a4f29
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
screen_protector: 6f92086bd2f2f4b54f54913289b9d1310610140b
screen_protector: 3d90d44ac886b25335aebd93230b454aef45794a
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
Sentry: ebc12276bd17613a114ab359074096b6b3725203
sentry_flutter: 88ebea3f595b0bc16acc5bedacafe6d60c12dcd5
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
tobias: 22f69183b14af82a2ac8ff698af084c352744e5f
shared_preferences_foundation: 0b09b969fb36da5551c0bc4a2dbd9d0ff9387478
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
tobias: 50b529c2501d277c83fef9976803a001eb58a057
WechatOpenSDK-XCFramework: acdeeda129efbef9532bca8a10c24e1b4b8c7d69
PODFILE CHECKSUM: 293dca100fbb3eb6a866f640cf85127844630abc
COCOAPODS: 1.11.2
COCOAPODS: 1.16.2
......@@ -37,8 +37,10 @@ class LoginController extends GetxController {
/// 测试账号
if (kDebugMode) {
phoneInput.text = '18810760819';
passwordInput.text = '1234567m';
phoneInput.text = '13811534871';
passwordInput.text = 'wpf123456*';
// phoneInput.text = '18810760819';
// passwordInput.text = '1234567m';
// phoneInput.text = '17311837355';
// passwordInput.text = '12345678';
}
......@@ -128,7 +130,8 @@ class LoginController extends GetxController {
}
final result = await AccountAPI.login(
phone: phoneInput.text,
password: EncryptUtil.encodeMd5(EncryptUtil.encodeMd5(passwordInput.text)),
password:
EncryptUtil.encodeMd5(EncryptUtil.encodeMd5(passwordInput.text)),
code: codeInput.text,
type: type);
......
/*
import 'dart:async';
import 'dart:io';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluwx/fluwx.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:tobias/tobias.dart' as tobias;
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:ace_word/utils/http.dart';
import 'package:ace_word/utils/global_data.dart' as globals;
import 'package:ace_word/pages/browser.dart';
import 'package:ace_word/components/ace/navibar/index.dart';
import 'package:tobias/tobias.dart';
class Vip extends StatefulWidget {
const Vip({Key? key}) : super(key: key);
@override
State<Vip> createState() => _VipState();
}
/// 去除ListView的顶部阴影
class MyBehavior extends ScrollBehavior {
@override
Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
return child;
}
}
class _VipState extends State<Vip> {
int chosenIndex = 3;
Map settings = {};
List payInfoList = [];
String orderNo = '';
String payMethod = 'wechat-pay';
String? userID;
var inAppPurchaseInstance = InAppPurchase.instance;
late StreamSubscription<List<PurchaseDetails>> _subscription;
/// 初始化事件
Future<void> init() async {
// setData: userID & settings
settings = await HTTP(url: '/settings').request();
userID = await HTTP(url: '/user/id').request();
setState(() {
settings = settings;
userID = userID;
});
// print(settings);
// print(userID);
// setData: userID & settings
payInfoList = await HTTP(url: '/pay/info').request();
setState(() {
payInfoList = payInfoList;
});
print(payInfoList);
final Stream<List<PurchaseDetails>> purchaseUpdated = inAppPurchaseInstance.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
// 监听微信支付回调
weChatResponseEventHandler.listen((event) async {
// 支付成功
if (event.errCode == 0) {
try {
var res = await HTTP(url: '/pay/status').request();
EasyLoading.showSuccess('支付成功');
globals.needRefreshMinePage = true;
setState(() {
settings['vipExpireDate'] = res['expireDate'];
});
} catch (err) {
var errMap = err as Map;
if (errMap['errcode'] == 419) {
EasyLoading.showError('支付未完成');
}
}
} else {
if (event.errCode == -2) {
EasyLoading.showError('取消支付');
}
}
});
}
// 要把localVerificationData发到服务端,验证
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
try {
var result = await HTTP(url: '/pay/ios/notify', method: 'POST', data: {
'orderNo': orderNo,
'receipt': purchaseDetails.verificationData.serverVerificationData,
}).request();
return result['errcode'] == 0;
} catch (e) {
return false;
}
}
// iOS支付监听
Future<void> _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
for (var purchaseDetails in purchaseDetailsList) {
// 支付进行中
if (purchaseDetails.status == PurchaseStatus.pending) {
// _showPendingUI();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
EasyLoading.showError('发生错误,请重试');
} else if (purchaseDetails.status == PurchaseStatus.canceled) {
EasyLoading.showError('支付取消');
} else if (purchaseDetails.status == PurchaseStatus.restored) {
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
inAppPurchaseInstance.completePurchase(purchaseDetails);
// 1. 用户购买成功,苹果服务器向用户手机发送令牌以后
// tips: 如果关机,信号差...需要本地持久化这个令牌,每次启动,检查本地是否有该令牌,如果有,向服务器发送开通会员请求,删掉
// 2. 用户拿着令牌,向我们自己服务器发送,并开通会员
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {
try {
Future.delayed(const Duration(milliseconds: 500));
var res = await HTTP(url: '/pay/status').request();
EasyLoading.showSuccess('支付成功');
globals.needRefreshMinePage = true;
setState(() {
settings['vipExpireDate'] = res['expireDate'];
});
} catch (err) {
var errMap = err as Map;
if (errMap['errcode'] == 419) {
EasyLoading.showError('支付未完成');
}
}
} else {
EasyLoading.showError('发生错误,请联系客服');
// _handleInvalidPurchase(purchaseDetails);
}
}
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
}
}
}
}
@override
void initState() {
super.initState();
init();
}
String _formatCreateDate(createDate) {
return createDate == null ? '' : createDate.substring(0, 4) + '.' + createDate.substring(4, 6) + '.' + createDate.substring(6);
}
@override
Widget build(BuildContext context) {
if (Theme.of(context).platform == TargetPlatform.android) {
SystemUiOverlayStyle style = const SystemUiOverlayStyle(statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.light);
SystemChrome.setSystemUIOverlayStyle(style);
}
return ScrollConfiguration(
behavior: MyBehavior(),
child: Scaffold(
body: Stack(children: [
Container(
decoration: const BoxDecoration(
color: Color(0xFF161A27),
),
),
Container(
width: double.infinity,
height: double.infinity,
margin: const EdgeInsets.only(top: 54),
decoration: const BoxDecoration(
color: Color(0xFF161A27),
),
),
Column(
children: [
SizedBox(height: MediaQuery.of(context).padding.top + 54 + 16),
Expanded(
child: Stack(children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Image.asset(
'images/vip/background.png',
width: MediaQuery.of(context).size.width - 32,
height: (MediaQuery.of(context).size.width - 32) * 162 / 343,
),
),
Positioned(
top: 46,
left: 16 + 16,
child: Row(
children: [
Container(
padding: const EdgeInsets.all(1), // Border width
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(100)),
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: SizedBox.fromSize(
size: const Size.fromRadius(26), // Image radius
child: globals.settings['avatarUrl'] == null
? const CircleAvatar(
radius: 1000,
backgroundColor: Colors.white,
backgroundImage: AssetImage('images/icons/avatar.png'),
)
: Image.asset(
'images/vip/avatar.png',
),
),
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${(settings['nickName'] == null || (userID == null)) ? '...' : settings['nickName'].isEmpty ? '词友_${userID!.substring(0, 6)}' : settings['nickName']}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF72522D),
),
),
const SizedBox(height: 2),
Text(
settings['vipExpireDate'] == '00000000' || int.parse(globals.settings['vipExpireDate']) < int.parse(globals.todayDate)
? '尚未开通会员'
: '有效期至${_formatCreateDate(settings['vipExpireDate'])}',
style: const TextStyle(
fontSize: 12,
color: Color(0xFFB58A5A),
),
),
],
),
],
),
),
Positioned(
top: 120,
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - (MediaQuery.of(context).padding.top + 54 + 16 + 120),
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
color: Color(0xFFFFFFFF),
borderRadius: BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0)),
),
child: ListView(
padding: const EdgeInsets.only(top: 24),
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'解锁会员',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
const SizedBox(height: 20),
SizedBox(
width: MediaQuery.of(context).size.width - 32,
height: 92,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
chosenIndex = 3;
});
},
child: Container(
width: MediaQuery.of(context).size.width - 32,
height: 92,
decoration: BoxDecoration(
color: chosenIndex == 3 ? const Color(0xFFFEF9E9) : const Color(0xFFFFFFFF),
border: Border.all(
color: chosenIndex == 3 ? const Color(0xFFAC884E) : const Color(0xFFE2E2E2),
width: 1.0,
),
borderRadius: const BorderRadius.all(Radius.circular(14.0)),
),
child: Stack(children: [
if (chosenIndex == 3)
Positioned(
top: 0,
right: 0,
child: Image.asset(
'images/vip/check.png',
width: 24,
height: 20,
),
),
SizedBox(
height: 92,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'终身会员',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF434346),
),
),
SizedBox(height: 4),
Text(
'您将永久解锁本软件的所有会员权益',
style: TextStyle(
fontSize: 11,
color: Color(0xFF86909C),
),
),
],
),
),
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'images/vip/rmb.png',
width: 16,
height: 14,
),
Text(
'${payInfoList.isEmpty ? '...' : payInfoList[3]['money'] ~/ 100}',
style: const TextStyle(
fontFamily: 'SFProDisplay',
fontSize: 28,
fontWeight: FontWeight.w700,
color: Color(0xFFBB8247),
),
),
const SizedBox(width: 4),
],
),
const Text(
'¥168',
style: TextStyle(
fontSize: 12,
color: Color(0xFF939495),
decoration: TextDecoration.lineThrough,
),
),
],
),
),
]),
),
]),
),
),
],
),
),
const SizedBox(height: 16),
SizedBox(
width: MediaQuery.of(context).size.width - 32,
height: 138,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
chosenIndex = 0;
});
},
child: Container(
width: (MediaQuery.of(context).size.width - 32 - 30) / 3,
decoration: BoxDecoration(
color: chosenIndex == 0 ? const Color(0xFFFEF9E9) : const Color(0xFFFFFFFF),
border: Border.all(
color: chosenIndex == 0 ? const Color(0xFFAC884E) : const Color(0xFFE2E2E2),
width: 1.0,
),
borderRadius: const BorderRadius.all(Radius.circular(14.0) // <--- border radius here
),
),
child: Stack(children: [
if (chosenIndex == 0)
Positioned(
top: 0,
right: 0,
child: Image.asset(
'images/vip/check.png',
width: 24,
height: 20,
),
),
Positioned(
top: 20,
child: SizedBox(
width: (MediaQuery.of(context).size.width - 32 - 30) / 3,
child: Column(children: [
const Text(
'12个月',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF434346),
),
),
const SizedBox(height: 6),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'images/vip/rmb.png',
width: 16,
height: 14,
),
Text(
'${payInfoList.isEmpty ? '...' : payInfoList[0]['money'] ~/ 100}',
style: const TextStyle(
fontFamily: 'SFProDisplay',
fontSize: 28,
fontWeight: FontWeight.w700,
color: Color(0xFFBB8247),
),
),
const SizedBox(width: 4),
],
),
const SizedBox(height: 6),
const Text(
'¥98',
style: TextStyle(
fontSize: 12,
color: Color(0xFF939495),
decoration: TextDecoration.lineThrough,
),
),
]),
),
),
Positioned(
bottom: 0,
child: Container(
width: (MediaQuery.of(context).size.width - 32 - 30) / 3 - 2,
height: 24,
decoration: BoxDecoration(
color: const Color(0xFFAC884E),
border: Border.all(
color: const Color(0xFFAC884E),
width: 1.0,
),
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(12.0),
),
),
child: const Center(
child: Text(
'优惠 30%',
style: TextStyle(
fontSize: 12,
color: Color(0xFFFFFFFF),
),
),
),
),
),
]),
),
),
GestureDetector(
onTap: () {
setState(() {
chosenIndex = 1;
});
},
child: Container(
width: (MediaQuery.of(context).size.width - 32 - 30) / 3,
decoration: BoxDecoration(
color: chosenIndex == 1 ? const Color(0xFFFEF9E9) : const Color(0xFFFFFFFF),
border: Border.all(
color: chosenIndex == 1 ? const Color(0xFFAC884E) : const Color(0xFFE2E2E2),
width: 1.0,
),
borderRadius: const BorderRadius.all(Radius.circular(14.0) // <--- border radius here
),
),
child: Stack(children: [
if (chosenIndex == 1)
Positioned(
top: 0,
right: 0,
child: Image.asset(
'images/vip/check.png',
width: 24,
height: 20,
),
),
Positioned(
top: 20,
child: SizedBox(
width: (MediaQuery.of(context).size.width - 32 - 30) / 3,
child: Column(children: [
const Text(
'3个月',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF434346),
),
),
const SizedBox(height: 6),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'images/vip/rmb.png',
width: 16,
height: 14,
),
Text(
'${payInfoList.isEmpty ? '...' : payInfoList[1]['money'] ~/ 100}',
style: const TextStyle(
fontFamily: 'SFProDisplay',
fontSize: 28,
fontWeight: FontWeight.w700,
color: Color(0xFFBB8247),
),
),
const SizedBox(width: 4),
],
),
const SizedBox(height: 6),
const Text(
'¥36',
style: TextStyle(
fontSize: 12,
color: Color(0xFF939495),
decoration: TextDecoration.lineThrough,
),
),
]),
),
),
Positioned(
bottom: 0,
child: Container(
width: (MediaQuery.of(context).size.width - 32 - 30) / 3 - 2,
height: 24,
decoration: BoxDecoration(
color: const Color(0xFFAC884E),
border: Border.all(
color: const Color(0xFFAC884E),
width: 1.0,
),
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(12.0),
),
),
child: const Center(
child: Text(
'优惠 16%',
style: TextStyle(
fontSize: 12,
color: Color(0xFFFFFFFF),
),
),
),
),
),
]),
),
),
GestureDetector(
onTap: () {
setState(() {
chosenIndex = 2;
});
},
child: Container(
width: (MediaQuery.of(context).size.width - 32 - 30) / 3,
decoration: BoxDecoration(
color: chosenIndex == 2 ? const Color(0xFFFEF9E9) : const Color(0xFFFFFFFF),
border: Border.all(
color: chosenIndex == 2 ? const Color(0xFFAC884E) : const Color(0xFFE2E2E2),
width: 1.0,
),
borderRadius: const BorderRadius.all(Radius.circular(14.0) // <--- border radius here
),
),
child: Stack(children: [
if (chosenIndex == 2)
Positioned(
top: 0,
right: 0,
child: Image.asset(
'images/vip/check.png',
width: 24,
height: 20,
),
),
Positioned(
top: 20,
child: SizedBox(
width: (MediaQuery.of(context).size.width - 32 - 30) / 3,
child: Column(children: [
const Text(
'1个月',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF434346),
),
),
const SizedBox(height: 6),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'images/vip/rmb.png',
width: 16,
height: 14,
),
Text(
'${payInfoList.isEmpty ? '...' : payInfoList[2]['money'] ~/ 100}',
style: const TextStyle(
fontFamily: 'SFProDisplay',
fontSize: 28,
fontWeight: FontWeight.w700,
color: Color(0xFFBB8247),
),
),
const SizedBox(width: 4),
],
),
const SizedBox(height: 6),
const Text(
'无优惠',
style: TextStyle(
fontSize: 12,
color: Color(0xFF939495),
// decoration: TextDecoration.lineThrough,
),
),
]),
),
),
]),
),
),
],
),
),
if (!Platform.isIOS) const SizedBox(height: 20),
if (!Platform.isIOS)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () async {
setState(() {
payMethod = 'wechat-pay';
});
},
child: Container(
width: (MediaQuery.of(context).size.width - 48 - 10) / 2,
height: 50,
padding: const EdgeInsets.fromLTRB(8, 0, 12, 0),
decoration: const BoxDecoration(
color: Color(0xFFF7F8FA),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
child: SizedBox(
width: (MediaQuery.of(context).size.width - 48 - 10) / 2,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Image.asset(
'images/vip/wechat-pay-fill.png',
width: 24,
height: 24,
),
const SizedBox(width: 6),
const Text(
'微信支付',
style: TextStyle(
fontSize: 14,
color: Color(0xFF1E2128),
),
),
],
),
Image.asset(
'images/vip/check-circle${payMethod == 'wechat-pay' ? '-fill' : ''}.png',
width: 16,
height: 16,
),
],
),
),
),
),
GestureDetector(
onTap: () async {
setState(() {
payMethod = 'alipay';
});
},
child: Container(
width: (MediaQuery.of(context).size.width - 48 - 10) / 2,
height: 50,
padding: const EdgeInsets.fromLTRB(8, 0, 12, 0),
decoration: const BoxDecoration(
color: Color(0xFFF7F8FA),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
child: SizedBox(
width: (MediaQuery.of(context).size.width - 48 - 10) / 2,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Image.asset(
'images/vip/alipay-fill.png',
width: 24,
height: 24,
),
const SizedBox(width: 6),
const Text(
'支付宝支付',
style: TextStyle(
fontSize: 14,
color: Color(0xFF1E2128),
),
),
],
),
Image.asset(
'images/vip/check-circle${payMethod == 'alipay' ? '-fill' : ''}.png',
width: 16,
height: 16,
),
],
),
),
),
),
],
),
const SizedBox(height: 24),
GestureDetector(
onTap: () async {
var isIOS = Platform.isIOS || false;
if (isIOS) {
EasyLoading.show(status: "正在与Apple服务器通信");
// 0. 调用服务器下单接口 /pay/ios
var result = await HTTP(url: '/pay/1.0/ios?payment-id=${payInfoList[chosenIndex]['id']}').request();
orderNo = result['orderNo'];
var productSet = <String>{result['productID']};
ProductDetailsResponse response = await inAppPurchaseInstance.queryProductDetails(productSet);
// 只需要填写商品ID即可
final ProductDetails productDetail = response.productDetails.first;
// 5. 创建购买参数
final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetail);
// 6. 发起购买,这里要注意避免重复购买
inAppPurchaseInstance.restorePurchases(applicationUserName: result['productID']);
try {
InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam, autoConsume: true);
} catch (e) {
EasyLoading.showError("购买失败");
}
} else if (payMethod == 'wechat-pay') {
EasyLoading.show();
// 微信支付
var result = await HTTP(url: '/pay/1.0/wechat?payment-id=${payInfoList[chosenIndex]['id']}').request();
// print(result);
payWithWeChat(
appId: result['appid'],
partnerId: result['partnerid'],
prepayId: result['prepayid'],
packageValue: result['package'],
nonceStr: result['noncestr'],
timeStamp: int.parse(result['timestamp']),
sign: result['sign'],
);
} else if (payMethod == 'alipay') {
// 支付宝支付
EasyLoading.show();
var str = await HTTP(url: '/pay/1.0/alipay?payment-id=${payInfoList[chosenIndex]['id']}').request();
// print(str);
try {
await tobias.aliPay(str, evn: AliPayEvn.ONLINE);
var res = await HTTP(url: '/pay/status').request();
EasyLoading.showSuccess('支付成功');
setState(() {
settings['vipExpireDate'] = res['expireDate'];
});
} catch (err) {
// print(err);
var errMap = err as Map;
if (errMap['errcode'] == 419) {
EasyLoading.showError('支付未完成');
}
}
}
},
child: Container(
width: MediaQuery.of(context).size.width,
height: 52,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [-0.16, 1.36],
colors: [
Color(0xFFFAD79D),
Color(0xFFFFC96F),
],
),
borderRadius: BorderRadius.all(Radius.circular(14.0)),
),
child: Center(
child: Text(
'立即以${chosenIndex == 0 ? 68 : chosenIndex == 1 ? 25 : chosenIndex == 2 ? 12 : 128}元开通',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF7D6032),
),
),
),
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RichText(
textAlign: TextAlign.center,
text: TextSpan(
children: [
const TextSpan(style: TextStyle(color: Color(0xFF787878), fontSize: 14), text: '开通代表接受'),
TextSpan(
text: '《AceWord会员协议》',
style: const TextStyle(color: Color(0xFFBB8247), fontSize: 14),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const Browser(
url:
'https://mp.weixin.qq.com/s?__biz=MzkzNDQwMzgxNw==&mid=2247483703&idx=1&sn=43cc958a0373ddf44ca7706ac2a9db05&chksm=c2bc8ab1f5cb03a789c1d1ff8be6f2d76796859b7a7b55aad2f196f63b349f49579690794232&token=430278592&lang=zh_CN#rd',
title: '会员协议'),
),
);
},
),
],
),
),
],
),
const SizedBox(height: 24),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'会员特权',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
const SizedBox(height: 20),
Row(
children: [
Image.asset(
'images/vip/feature-1.png',
width: 40,
height: 40,
),
const SizedBox(width: 14),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'无限卡片用量',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
SizedBox(height: 2),
Text(
'每日新建的卡片数量无上限',
style: TextStyle(
fontSize: 13,
color: Color(0xFF929499),
),
),
],
),
],
),
const SizedBox(height: 24),
Row(
children: [
Image.asset(
'images/vip/feature-2.png',
width: 40,
height: 40,
),
const SizedBox(width: 14),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'无限单词本、自定义词书用量',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
SizedBox(height: 2),
Text(
'创建的单词本、自定义词书数量均无上限',
style: TextStyle(
fontSize: 13,
color: Color(0xFF929499),
),
),
],
),
],
),
const SizedBox(height: 24),
Row(
children: [
Image.asset(
'images/vip/feature-3.png',
width: 40,
height: 40,
),
const SizedBox(width: 14),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'批量导入自定义释义',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
SizedBox(height: 2),
Text(
'可以通过自定义词书批量导入自定义释义',
style: TextStyle(
fontSize: 13,
color: Color(0xFF929499),
),
),
],
),
],
),
const SizedBox(height: 24),
Row(
children: [
Image.asset(
'images/vip/feature-4.png',
width: 40,
height: 40,
),
const SizedBox(width: 14),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'自定义复习节奏(倒计时)',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
SizedBox(height: 2),
Text(
'可以调整复习节奏更紧凑,或更松弛',
style: TextStyle(
fontSize: 13,
color: Color(0xFF929499),
),
),
],
),
],
),
const SizedBox(height: 24),
Row(
children: [
Image.asset(
'images/vip/feature-4.png',
width: 40,
height: 40,
),
const SizedBox(width: 14),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'丰富的卡片英文字体',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
SizedBox(height: 2),
Text(
'可以切换八种不同的卡片英文字体',
style: TextStyle(
fontSize: 13,
color: Color(0xFF929499),
),
),
],
),
],
),
const SizedBox(height: 24),
Row(
children: [
Image.asset(
'images/vip/feature-6.png',
width: 40,
height: 40,
),
const SizedBox(width: 14),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'丰富的单词背景色',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1D2129),
),
),
SizedBox(height: 2),
Text(
'可以给卡片单词标记多种背景色',
style: TextStyle(
fontSize: 13,
color: Color(0xFF929499),
),
),
],
),
],
),
const SizedBox(height: 24),
],
),
],
)),
),
]),
),
],
),
Padding(
padding: EdgeInsets.only(top: MediaQuery.of(context).viewPadding.top),
child: const NaviBar(type: 'back', showDivider: false, showVantDivider: false, title: '开通会员', backgroundColor: Color(0xFF161A27), titleTextColor: Color(0xFFFFDB93))),
]),
),
);
}
}
*/
......@@ -6,21 +6,21 @@ class UserStore extends GetxController {
// 是否登录
bool _isLogin = false;
// token
String _token = '';
// 获取token
String get token => _token;
// 刷新token
String _accessToken = '';
// 获取刷新token
String get accessToken => _accessToken;
// 是否登录
bool get isLogin => _isLogin;
// 是否有token
bool get hasToken => _token.isNotEmpty;
// 用户信息模型
UserModel _info = UserModel();
// 获取用户信息模型
UserModel get info => _info;
// 是否需要登录
......@@ -34,6 +34,7 @@ class UserStore extends GetxController {
_token = StorageService.to.getString(kLocalToken);
}
/// 登录
Future<void> login(UserModel value) async {
_token = value.token ?? '';
_accessToken = value.accessToken ?? '';
......@@ -48,16 +49,18 @@ class UserStore extends GetxController {
update();
}
/// 设置token
Future<void> setToken(String value) async {
await StorageService.to.setString(kLocalToken, value);
_token = value;
}
/// 设置刷新token
Future<void> setAccessToken(String value) async {
await StorageService.to.setString(kLocalAccessToken, value);
_accessToken = value;
}
/// 设置用户信息
Future<void> setInfo(UserModel value) async {
await StorageService.to
.setString(kLocalUserInfo, jsonEncode(value.toJson()));
......@@ -65,7 +68,7 @@ class UserStore extends GetxController {
_info = value;
}
// 登出
/// 登出
Future<void> logout() async {
await StorageService.to.remove(kLocalToken);
await StorageService.to.remove(kLocalAccessToken);
......
......@@ -50,7 +50,7 @@ abstract class AssetsPicker {
// maxHeight: maxHeight,
);
}
/// 展示设置弹窗
static _showSettingDialog(BuildContext context,String content) {
showDialog(
context: context,
......
part of utils;
/// 打印类
abstract class Console {
static final _log = Logger(filter: _LogFilter());
......
......@@ -3,7 +3,7 @@ part of utils;
// 服务器地址
// const String kServerUrl = 'http://192.168.11.88:81';
// const String kServerUrl = 'http://8.141.148.247:7421';
const String kServerUrl = 'http://1507.superge.cn:81';
const String kServerUrl = 'https://ebook-app.ezijing.com';
const String kHtmlBaseServer = 'http://150.158.138.40:9200';
const String kLocalToken = 'local_token';
const String kLocalAccessToken = 'local_access_token';
......
......@@ -15,7 +15,7 @@ class OssTool {
Client.init(ossEndpoint: 'oss-cn-beijing.aliyuncs.com', bucketName:bucketName,authGetter: _authGetter);
}
// 获取临时凭证
/// 获取临时凭证
Future<Auth> _authGetter() async {
final result = await CommonAPI.oss();
return Auth(
......@@ -26,7 +26,7 @@ class OssTool {
);
}
// 本地文件上传
/// 本地文件上传
Future<Response<dynamic>> putObjectFile(String filePath) async {
List<String> parts = filePath.split('/');
String fileName = parts.last;
......@@ -38,7 +38,7 @@ class OssTool {
return resp;
}
// 批量本地文件上传
/// 批量本地文件上传
Future<List<Response>> putObjectFiles(List<String> filePaths){
return Client().putObjectFiles(
filePaths.map((e){
......
part of utils;
class SignTool {
/// 创建签名
static String createSign(Map<String, dynamic> params) {
Map<String, dynamic> tempParams = keySort(params);
List<String> tempArr = [];
......
......@@ -58,13 +58,13 @@ class SqlManager {
return _database;
}
// 关闭数据库
/// 关闭数据库
static Future<void> closeDatabase() async {
await _database?.close();
}
// 查询划线高亮笔记
/// 查询划线高亮笔记
static Future<Map<String, dynamic>> queryLocalNote({required int bookId,required int chapterId}) async {
Database? db = await SqlManager.getCurrentDatabase();
......@@ -89,7 +89,7 @@ class SqlManager {
return returnMap;
}
// 添加划线高亮笔记
/// 添加划线高亮笔记
static Future<int> addLocalNote(Map<String, dynamic> data) async {
Database? db = await SqlManager.getCurrentDatabase();
final result = await db?.insert(
......@@ -100,7 +100,7 @@ class SqlManager {
return result??0;
}
// 删除划线高亮笔记
/// 删除划线高亮笔记
static Future<int> delLocalNote({required int noteId,required int id}) async {
Database? db = await SqlManager.getCurrentDatabase();
if(noteId == 0){
......@@ -124,7 +124,7 @@ class SqlManager {
}
// 修改划线高亮笔记
/// 修改划线高亮笔记
static Future<int> updateLocalNote({required int notesId,required int id,required Map<String, dynamic> data}) async {
Database? db = await SqlManager.getCurrentDatabase();
if(notesId ==0){
......@@ -148,7 +148,7 @@ class SqlManager {
}
// 查询所有没有上传的数据
/// 查询所有没有上传的数据
static Future<List<Map<String, dynamic>>> queryNoUploadData() async {
Database? db = await SqlManager.getCurrentDatabase();
List<Map<String, dynamic>>? results = await db?.query(
......@@ -164,7 +164,7 @@ class SqlManager {
return results ??[];
}
// 将所有 upload 为 0 的数据的 upload 字段值更新为 1
/// 将所有 upload 为 0 的数据的 upload 字段值更新为 1
static Future<void> updateUploadStatus() async {
Database? db = await SqlManager.getCurrentDatabase();
final result = await db?.update(
......@@ -175,7 +175,7 @@ class SqlManager {
);
Console.log('Sql---------------更新数据----------------$result');
}
// 将上传成功的id数据的notes_id 更新为服务器对应数值 并设置upload 为1
/// 将上传成功的id数据的notes_id 更新为服务器对应数值 并设置upload 为1
static Future<void> updateNotesId(List<Map<String,dynamic>> data) async {
Database? db = await SqlManager.getCurrentDatabase();
// 构建批量更新的SQL语句
......@@ -205,7 +205,7 @@ class SqlManager {
db?.delete(kNoteTable);
db?.delete(kReadTable);
}
// 根据 book_id 查询当前读到的 章节
/// 根据 book_id 查询当前读到的 章节
static Future<String> queryReadHistoryByBookId(int bookId) async {
try {
Database? db = await SqlManager.getCurrentDatabase();
......@@ -226,7 +226,7 @@ class SqlManager {
}
// 根据 book_id 写入当前读到的 章节
/// 根据 book_id 写入当前读到的 章节
static Future<int> updateReadHistoryByBookId(int bookId ,int chapterId) async {
Database? db = await SqlManager.getCurrentDatabase();
......@@ -256,7 +256,7 @@ class SqlManager {
// 插入数据
/// 插入数据
static Future<bool> insertData(Map<String, dynamic> data) async {
Database? db = await SqlManager.getCurrentDatabase();
final result = await db?.insert(
......@@ -271,13 +271,13 @@ class SqlManager {
}
// 查询所有数据
/// 查询所有数据
static Future<List<Map<String, dynamic>>?> queryAllData() async {
Database? db = await SqlManager.getCurrentDatabase();
return await db?.query('members_book_notes');
}
// 根据 ID 查询数据
/// 根据 ID 查询数据
static Future<Map<String, dynamic>?> queryDataById(int id) async {
Database? db = await SqlManager.getCurrentDatabase();
List<Map<String, dynamic>>? results = await db?.query(
......@@ -288,7 +288,7 @@ class SqlManager {
return results?.first??{};
}
// 更新数据
/// 更新数据
static Future<void> updateData(int id, Map<String, dynamic> newData) async {
Database? db = await SqlManager.getCurrentDatabase();
await db?.update(
......@@ -299,7 +299,7 @@ class SqlManager {
);
}
// 删除数据
/// 删除数据
static Future<void> deleteData(int id) async {
Database? db = await SqlManager.getCurrentDatabase();
await db?.delete(
......
......@@ -2,6 +2,7 @@ part of utils;
/// Toast工具类
class Toast {
/// 展示toast
static void show(String msg, {int duration = 2000}) {
if (msg == null) {
return;
......@@ -13,6 +14,7 @@ class Toast {
);
}
/// 取消toast
static void cancelToast() {
dismissAllToast();
}
......
......@@ -2,10 +2,12 @@ part of utils;
abstract class Tools {
/// 取消焦点
static void unfocus() {
WidgetsBinding.instance.focusManager.primaryFocus?.unfocus();
}
/// 格式化日期
static String dateFromMS(
int timestamp, {
String pattern = 'yyyy-MM-dd',
......@@ -29,6 +31,7 @@ abstract class Tools {
return DateFormat(pattern).format(dateTime);
}
/// 格式化时间
static String formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, '0');
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
......@@ -36,26 +39,28 @@ abstract class Tools {
return "${duration.inHours}:$twoDigitMinutes:$twoDigitSeconds";
}
/// 获取存储目录
static Future<String> getDirectory() async {
// getTemporaryDirectory
final directory = await getTemporaryDirectory();
return directory!.path;
}
// 语音文件名称
/// 语音文件名称
static String generateVoiceFileName(){
DateTime now = DateTime.now();
String formattedDate = DateFormat('yyyyMMddHHmmss').format(now);
return 'voice_$formattedDate.mp4';
}
/// 清理数据
static Future<void> clearData() async {
await SqlManager.clear();
ClearCache().clearApplicationCache();
Console.log('-------------清除数据-------------------');
}
// 判断当前网络状态
/// 判断当前网络状态
static Future<bool> checkCurrentNetStatus() async {
final connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none){
......
......@@ -29,6 +29,7 @@ class RequiredValidator extends Validator<String?> {
bool isValid(String? value) => (value ?? '').isNotEmpty;
}
/// 邮箱验证
class EmailValidator extends Validator<String?> {
final Pattern _pattern =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
......@@ -42,6 +43,7 @@ class EmailValidator extends Validator<String?> {
}
}
/// 密码验证
class PasswordValidator extends Validator<String?> {
final Pattern _pattern = r'(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$';
......@@ -55,6 +57,7 @@ class PasswordValidator extends Validator<String?> {
}
}
/// 是否一致
class EqualValidator extends Validator<String?> {
final TextEditingController input;
......@@ -83,19 +86,20 @@ class AmountValidator extends Validator<String?> {
class ValidatorTool {
// 验证手机号
/// 验证手机号
static bool isValidPhoneNumber(String value) {
RegExp phonePattern = RegExp(r'^1[3-9]\d{9}$');
return phonePattern.hasMatch(value);
}
// 密码
/// 密码
static bool isValidPassword(String value) {
RegExp passwordPattern = RegExp(r'^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$');
// 密码必须是数字、字母、特殊符号三种及以上8-12位的组合
return passwordPattern.hasMatch(value);
}
/// 验证码位数
static bool isValidCode(String value) {
RegExp passwordPattern = RegExp(r'^\d{6}$');
return passwordPattern.hasMatch(value);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论