Commit ad37b6538c6ae4c2ea19c971845f4baa4cebd8d9
1 parent
e62c1a97
feat:1、完成微信支付;2、商品详情页ui优化
Showing
6 changed files
with
196 additions
and
95 deletions
lib/common/request/apis.dart
lib/common/request/dao/shop_dao.dart
... | ... | @@ -13,9 +13,15 @@ class ShopDao { |
13 | 13 | .post<Map<String, dynamic>>(Apis.createOrder, data: {'courseComboId': productEntity.id}); |
14 | 14 | } |
15 | 15 | |
16 | - ///获取ali支付订单信息 | |
16 | + ///获取alipay支付订单信息 | |
17 | 17 | static Future getAliPayToken(String orderNo) async { |
18 | 18 | return await requestClient |
19 | 19 | .post<Map<String, dynamic>>(Apis.getAliPayToken, data: {'orderNo': orderNo}); |
20 | 20 | } |
21 | + | |
22 | + ///获取weixin支付订单信息 | |
23 | + static Future getWxPayToken(String orderNo) async { | |
24 | + return await requestClient | |
25 | + .post<Map<String, dynamic>>(Apis.getWxPayToken, data: {'orderNo': orderNo}); | |
26 | + } | |
21 | 27 | } | ... | ... |
lib/pages/reading/bloc/reading_bloc.dart
... | ... | @@ -247,7 +247,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
247 | 247 | final recordAudioUrl = currentPageData()?.recordUrl; |
248 | 248 | _playAudio(recordAudioUrl); |
249 | 249 | } |
250 | - // emit(VoicePlayStateChange()); | |
250 | + // emitter(VoicePlayStateChange()); | |
251 | 251 | } |
252 | 252 | |
253 | 253 | void _playAudio(String? audioUrl) async { |
... | ... | @@ -358,7 +358,7 @@ class ReadingPageBloc extends Bloc<ReadingPageEvent, ReadingPageState> { |
358 | 358 | OnXSVoiceStateChangeEvent event, |
359 | 359 | Emitter<ReadingPageState> emitter |
360 | 360 | ) async { |
361 | - emit(XSVoiceTestState()); | |
361 | + emitter(XSVoiceTestState()); | |
362 | 362 | } |
363 | 363 | } |
364 | 364 | ... | ... |
lib/pages/shopping/bloc.dart
1 | +import 'dart:io'; | |
2 | + | |
1 | 3 | import 'package:bloc/bloc.dart'; |
4 | +import 'package:flutter/cupertino.dart'; | |
2 | 5 | import 'package:fluwx/fluwx.dart'; |
3 | 6 | import 'package:tobias/tobias.dart'; |
4 | 7 | import 'package:wow_english/generated/json/base/json_convert_content.dart'; |
... | ... | @@ -22,6 +25,11 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
22 | 25 | |
23 | 26 | PaymentChannel get curPaymentChannel => _curPaymentChannel; |
24 | 27 | |
28 | + Fluwx? fluwx; | |
29 | + Function(WeChatResponse)? wxPayResponseListener; | |
30 | + bool _isWxPayListenerInitialized = false; | |
31 | + | |
32 | + | |
25 | 33 | |
26 | 34 | ShoppingBloc(this._productData) : super(ShoppingState().init()) { |
27 | 35 | //页面初始化时刻 |
... | ... | @@ -66,19 +74,53 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
66 | 74 | Log.d("orderNo $orderNo"); |
67 | 75 | |
68 | 76 | if (event.paymentChannel == PaymentChannel.wechatPay) { |
69 | - Fluwx fluwx = Fluwx(); | |
70 | - fluwx.registerApi(appId: "wxd930ea5d5a228f5f", | |
71 | - universalLink: "https://app-api.wowenglish.com.cn/.well-known/apple-app-site-association"); | |
72 | - // fluwx.pay( | |
73 | - // which: Payment( | |
74 | - // appId: _orderInfo['appid'].toString(), | |
75 | - // partnerId: _orderInfo['partnerid'].toString(), | |
76 | - // prepayId: _orderInfo['prepayid'].toString(), | |
77 | - // packageValue: _orderInfo['package'].toString(), | |
78 | - // nonceStr: _orderInfo['noncestr'].toString(), | |
79 | - // timestamp: _orderInfo['timestamp'], | |
80 | - // sign: _orderInfo['sign'].toString(), | |
81 | - // )); | |
77 | + if (_isWxPayListenerInitialized == false) { | |
78 | + _isWxPayListenerInitialized = true; | |
79 | + fluwx = Fluwx(); | |
80 | + fluwx?.registerApi(appId: "wx365e5a79956a450a", | |
81 | + universalLink: "https://app-api.wowenglish.com.cn/app/"); | |
82 | + wxPayResponseListener = (WeChatResponse response) { | |
83 | + Log.d("wxPayResponseListener $response"); | |
84 | + if (response is WeChatPaymentResponse) { | |
85 | + if (response.errCode == 0) { | |
86 | + Log.d("wxPayResponseListener response=${response.errCode}"); | |
87 | + showToast("支付成功"); | |
88 | + // Log.d("emitter isDone=${emitter.isDone}"); | |
89 | + // emitter(PaySuccessState()); | |
90 | + } else { | |
91 | + showToast("支付失败"); | |
92 | + } | |
93 | + } | |
94 | + }; | |
95 | + fluwx?.addSubscriber(wxPayResponseListener!); | |
96 | + } | |
97 | + | |
98 | + bool installed = await fluwx?.isWeChatInstalled == true; | |
99 | + if (!installed) { | |
100 | + // 未安装微信,请前去下载 | |
101 | + String name = Platform.isIOS ? "AppStore" : "应用商店"; | |
102 | + showToast("您未安装微信,请前往$name下载~"); | |
103 | + return; | |
104 | + } | |
105 | + | |
106 | + final Map<String, dynamic> wxPayOrderInfo = await ShopDao.getWxPayToken(orderNo); | |
107 | + Log.d("wxPayOrderInfo=$wxPayOrderInfo type=${wxPayOrderInfo.runtimeType}"); | |
108 | + final String? wxPayInfo = wxPayOrderInfo.getOrNull("appId"); | |
109 | + if (wxPayInfo == null) { | |
110 | + showToast("微信订单创建失败"); | |
111 | + return; | |
112 | + } | |
113 | + | |
114 | + await fluwx?.pay( | |
115 | + which: Payment( | |
116 | + appId: wxPayOrderInfo['appId'].toString(), | |
117 | + partnerId: wxPayOrderInfo['partnerId'].toString(), | |
118 | + prepayId: wxPayOrderInfo['prepayId'].toString(), | |
119 | + packageValue: wxPayOrderInfo['packageValue'].toString(), | |
120 | + nonceStr: wxPayOrderInfo['nonceStr'].toString(), | |
121 | + timestamp: int.parse(wxPayOrderInfo['timeStamp']), | |
122 | + sign: wxPayOrderInfo['sign'].toString(), | |
123 | + )); | |
82 | 124 | } else { |
83 | 125 | final Map<String, dynamic> aliPayOrderInfo = await ShopDao.getAliPayToken(orderNo); |
84 | 126 | Log.d("aliPayOrderInfo=$aliPayOrderInfo type=${aliPayOrderInfo.runtimeType}"); |
... | ... | @@ -95,7 +137,7 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
95 | 137 | // 判断resultStatus 为9000则代表支付成功 |
96 | 138 | if (aliPayResult.getOrNull("resultStatus") == "9000") { |
97 | 139 | showToast("支付成功"); |
98 | - emit(PaySuccessState()); | |
140 | + emitter(PaySuccessState()); | |
99 | 141 | } else { |
100 | 142 | showToast("支付失败"); |
101 | 143 | } |
... | ... | @@ -107,6 +149,12 @@ class ShoppingBloc extends Bloc<ShoppingEvent, ShoppingState> { |
107 | 149 | } |
108 | 150 | } |
109 | 151 | } |
152 | + | |
153 | + void dispose() { | |
154 | + fluwx?.clearSubscribers(); | |
155 | + // 释放资源的逻辑 | |
156 | + debugPrint('BLoC disposed'); | |
157 | + } | |
110 | 158 | } |
111 | 159 | |
112 | 160 | enum PaymentChannel { | ... | ... |
lib/pages/shopping/view.dart
... | ... | @@ -8,7 +8,6 @@ import 'package:wow_english/models/product_entity.dart'; |
8 | 8 | import '../../common/core/assets_const.dart'; |
9 | 9 | import '../../common/widgets/we_app_bar.dart'; |
10 | 10 | import '../../utils/image_util.dart'; |
11 | -import '../../utils/log_util.dart'; | |
12 | 11 | import 'bloc.dart'; |
13 | 12 | import 'event.dart'; |
14 | 13 | import 'state.dart'; |
... | ... | @@ -17,33 +16,36 @@ Widget buildRadioOption({ |
17 | 16 | required int value, |
18 | 17 | required ImageProvider icon, |
19 | 18 | required String text, |
20 | - required int groupValue, | |
21 | - required ValueChanged onChanged, | |
19 | + required bool isSelected, | |
20 | + required ValueChanged onSelect, | |
22 | 21 | }) { |
23 | - return Row( | |
24 | - children: [ | |
25 | - Image(image: icon, | |
26 | - width: 20.0.w, | |
27 | - height: 20.0.h, | |
28 | - fit: BoxFit.contain), | |
29 | - const SizedBox(width: 10.0), | |
30 | - // Expanded( | |
31 | - // child: Text( | |
32 | - // text, | |
33 | - // style: TextStyle(color: Color(0xFF333333), fontSize: 12.5.sp), | |
34 | - // ), | |
35 | - // ), | |
36 | - Text( | |
37 | - text, | |
38 | - style: TextStyle(color: Color(0xFF333333), fontSize: 12.5.sp), | |
39 | - ), | |
40 | - Radio( | |
41 | - value: value, | |
42 | - groupValue: groupValue, | |
43 | - onChanged: onChanged | |
22 | + return | |
23 | + GestureDetector( | |
24 | + onTap: () { | |
25 | + onSelect(value); | |
26 | + }, | |
27 | + child: Row( | |
28 | + crossAxisAlignment: CrossAxisAlignment.center, | |
29 | + children: [ | |
30 | + Image(image: icon, width: 25.0.w, height: 25.0.h, fit: BoxFit.contain), | |
31 | + const SizedBox(width: 10.0), | |
32 | + Expanded( | |
33 | + child: Text( | |
34 | + text, | |
35 | + style: TextStyle(color: const Color(0xFF333333), fontSize: 12.5.sp), | |
36 | + ), | |
37 | + ), | |
38 | + Image( | |
39 | + image: isSelected | |
40 | + ? AssetImage('checked'.assetPng) | |
41 | + : AssetImage('unchecked'.assetPng), | |
42 | + width: 22.0.w, | |
43 | + height: 22.0.h, | |
44 | + ), | |
45 | + ], | |
44 | 46 | ), |
45 | - ], | |
46 | - ); | |
47 | + ); | |
48 | + | |
47 | 49 | } |
48 | 50 | |
49 | 51 | class ShoppingPage extends StatelessWidget { |
... | ... | @@ -55,30 +57,26 @@ class ShoppingPage extends StatelessWidget { |
55 | 57 | Widget build(BuildContext context) { |
56 | 58 | return BlocProvider( |
57 | 59 | create: (BuildContext context) => |
58 | - ShoppingBloc(productEntity) | |
59 | - ..add(InitEvent()), | |
60 | + ShoppingBloc(productEntity)..add(InitEvent()), | |
60 | 61 | child: _ShoppingView(), |
61 | 62 | ); |
62 | 63 | } |
63 | 64 | } |
64 | 65 | |
65 | 66 | class _ShoppingView extends StatelessWidget { |
66 | - | |
67 | 67 | @override |
68 | 68 | Widget build(BuildContext context) { |
69 | 69 | final bloc = BlocProvider.of<ShoppingBloc>(context); |
70 | - // var title1 = bloc.productData?.name ?? ''; | |
71 | 70 | return BlocListener<ShoppingBloc, ShoppingState>( |
72 | 71 | listener: (context, state) { |
73 | - Log.d("wqf state=$state"); | |
74 | 72 | if (state is PaySuccessState) { |
75 | 73 | Navigator.pop(context); |
76 | 74 | } |
77 | 75 | }, |
78 | 76 | child: Scaffold( |
79 | - appBar: const WEAppBar( | |
77 | + appBar: WEAppBar( | |
80 | 78 | //标题传进来的 |
81 | - titleText: "商品详情", | |
79 | + titleText: bloc.productData?.name ?? '', | |
82 | 80 | ), |
83 | 81 | body: Container( |
84 | 82 | margin: const EdgeInsets.only(left: 80.0, top: 28.0, right: 56.0), |
... | ... | @@ -87,49 +85,63 @@ class _ShoppingView extends StatelessWidget { |
87 | 85 | children: [ |
88 | 86 | CachedNetworkImage( |
89 | 87 | imageUrl: bloc.productData?.detailPicUrl ?? '', |
90 | - imageBuilder: (context, imageProvider) => | |
91 | - Container( | |
92 | - decoration: BoxDecoration( | |
93 | - image: DecorationImage( | |
94 | - image: imageProvider, | |
95 | - fit: BoxFit.cover, | |
96 | - ), | |
97 | - borderRadius: BorderRadius.circular(5.0), | |
98 | - ), | |
88 | + imageBuilder: (context, imageProvider) => Container( | |
89 | + decoration: BoxDecoration( | |
90 | + image: DecorationImage( | |
91 | + image: imageProvider, | |
92 | + fit: BoxFit.cover, | |
99 | 93 | ), |
100 | - placeholder: (context, url) => const CircularProgressIndicator(), | |
94 | + borderRadius: BorderRadius.circular(5.0), | |
95 | + ), | |
96 | + ), | |
97 | + placeholder: (context, url) => | |
98 | + const CircularProgressIndicator(), | |
101 | 99 | // errorWidget: (context, url, error) => const Icon(Icons.error), |
102 | 100 | height: 210.0.h, |
103 | 101 | width: 210.0.w, |
104 | 102 | ), |
105 | 103 | const SizedBox(width: 35.5), |
106 | - _paymentWidget(), | |
104 | + Expanded(child: _paymentWidget()) | |
107 | 105 | ], |
108 | 106 | ), |
109 | 107 | ), |
110 | - ),); | |
108 | + ), | |
109 | + ); | |
111 | 110 | } |
112 | 111 | } |
113 | 112 | |
114 | -Widget _paymentWidget() => | |
115 | - BlocBuilder<ShoppingBloc, ShoppingState> | |
116 | - ( | |
113 | +Widget _paymentWidget() => BlocBuilder<ShoppingBloc, ShoppingState>( | |
117 | 114 | builder: (context, state) { |
118 | 115 | final bloc = BlocProvider.of<ShoppingBloc>(context); |
119 | 116 | return Column( |
120 | 117 | crossAxisAlignment: CrossAxisAlignment.start, |
121 | 118 | children: [ |
122 | - Text('套餐价格:${bloc.productData?.price ?? ''}', | |
123 | - style: TextStyle( | |
124 | - color: const Color(0xFF333333), fontSize: 16.sp)), | |
125 | - const SizedBox(height: 15.0), | |
126 | - Text('套餐名称:${bloc.productData?.name ?? ''}', | |
127 | - style: TextStyle( | |
128 | - color: const Color(0xFF333333), fontSize: 16.sp)), | |
129 | - const SizedBox(height: 15.0), | |
119 | + RichText( | |
120 | + text: TextSpan( | |
121 | + children: [ | |
122 | + TextSpan( | |
123 | + text: '套餐价格:', | |
124 | + style: | |
125 | + TextStyle(color: const Color(0xFF333333), fontSize: 16.5.sp) | |
126 | + ), | |
127 | + TextSpan( | |
128 | + text: '¥${bloc.productData?.price ?? ''}', | |
129 | + style: | |
130 | + TextStyle(color: const Color(0xFFE5262A), fontSize: 16.5.sp) | |
131 | + ), | |
132 | + ] | |
133 | + ), | |
134 | + ), | |
135 | + const SizedBox(height: 14.5), | |
136 | + Text( | |
137 | + '套餐名称:${bloc.productData?.name}', | |
138 | + style: | |
139 | + TextStyle(color: const Color(0xFF333333), fontSize: 16.sp), | |
140 | + maxLines: 2, | |
141 | + ), | |
142 | + const SizedBox(height: 14.5), | |
130 | 143 | Container( |
131 | - padding: | |
132 | - EdgeInsets.symmetric(horizontal: 6.w, vertical: 10.h), | |
144 | + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 10.h), | |
133 | 145 | decoration: BoxDecoration( |
134 | 146 | image: DecorationImage( |
135 | 147 | image: ImageUtil.getImageProviderOnDefault( |
... | ... | @@ -138,41 +150,61 @@ Widget _paymentWidget() => |
138 | 150 | child: const Text('支付方式选择', |
139 | 151 | style: TextStyle( |
140 | 152 | color: Color(0xFF333333), |
141 | - fontSize: 12, | |
153 | + fontSize: 12.5, | |
142 | 154 | fontFamily: 'PingFangSC-Regular')), |
143 | 155 | ), |
144 | - const SizedBox(height: 15.0), | |
156 | + const SizedBox(height: 18.0), | |
145 | 157 | buildRadioOption( |
146 | 158 | value: PaymentChannel.wechatPay.payChannelType, |
147 | 159 | icon: AssetImage('weixin'.assetPng), |
148 | 160 | text: PaymentChannel.wechatPay.payChannelName, |
149 | - groupValue: bloc.curPaymentChannel.payChannelType, | |
150 | - onChanged: (newValue) { | |
151 | - bloc.add( | |
152 | - ChangePaymentChannelEvent(PaymentChannel.wechatPay)); | |
161 | + isSelected: bloc.curPaymentChannel.payChannelType == PaymentChannel.wechatPay.payChannelType, | |
162 | + onSelect: (newValue) { | |
163 | + bloc.add(ChangePaymentChannelEvent(PaymentChannel.wechatPay)); | |
153 | 164 | }, |
154 | 165 | ), |
166 | + const SizedBox(height: 15.0), | |
155 | 167 | buildRadioOption( |
156 | 168 | value: PaymentChannel.aliPay.payChannelType, |
157 | 169 | icon: AssetImage('zhifubao'.assetPng), |
158 | 170 | text: PaymentChannel.aliPay.payChannelName, |
159 | - groupValue: bloc.curPaymentChannel.payChannelType, | |
160 | - onChanged: (newValue) { | |
171 | + isSelected: bloc.curPaymentChannel.payChannelType == PaymentChannel.aliPay.payChannelType, | |
172 | + onSelect: (newValue) { | |
161 | 173 | bloc.add(ChangePaymentChannelEvent(PaymentChannel.aliPay)); |
162 | 174 | }, |
163 | 175 | ), |
164 | - const SizedBox(height: 15.0), | |
165 | - // 确认支付按钮 | |
166 | - InkWell( | |
167 | - onTap: () { | |
168 | - Log.d('点击支付按钮 ${bloc.productData}'); | |
169 | - bloc.add(DoPayEvent(bloc.productData, bloc.curPaymentChannel)); | |
170 | - }, | |
171 | - child: Image( | |
172 | - width: 125.w, | |
173 | - height: 45.h, | |
174 | - image: AssetImage('btn_pay'.assetPng), | |
175 | - ), | |
176 | + const SizedBox(height: 20.0), | |
177 | + Row( | |
178 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
179 | + children: [ | |
180 | + RichText( | |
181 | + text: TextSpan( | |
182 | + children: [ | |
183 | + const TextSpan( | |
184 | + text: '需支付:', | |
185 | + style: | |
186 | + TextStyle(color: Color(0xFF333333), fontSize: 12.5, fontFamily: 'PingFangSC-Regular') | |
187 | + ), | |
188 | + TextSpan( | |
189 | + text: '¥${bloc.productData?.price ?? ''}', | |
190 | + style: | |
191 | + TextStyle(color: const Color(0xFFE7383B), fontSize: 41.sp, fontFamily: 'PingFangSC-Regular') | |
192 | + ), | |
193 | + ] | |
194 | + ), | |
195 | + ), | |
196 | + // 确认支付按钮 | |
197 | + GestureDetector( | |
198 | + onTap: () { | |
199 | + bloc.add(DoPayEvent(bloc.productData, bloc.curPaymentChannel)); | |
200 | + }, | |
201 | + child: Image( | |
202 | + width: 105.w, | |
203 | + height: 45.h, | |
204 | + image: AssetImage('btn_pay'.assetPng), | |
205 | + ), | |
206 | + ) | |
207 | + ], | |
176 | 208 | ), |
177 | 209 | ], |
178 | 210 | ); | ... | ... |
pubspec.yaml
... | ... | @@ -142,3 +142,15 @@ tobias: |
142 | 142 | url_scheme: ishowwoweng |
143 | 143 | ios: |
144 | 144 | ignore_security: true |
145 | + | |
146 | +fluwx: | |
147 | + app_id: 'wx365e5a79956a450a' | |
148 | + debug_logging: true # Logging in debug mode. | |
149 | + android: | |
150 | + # interrupt_wx_request: true # Defaults to true. | |
151 | + # flutter_activity: 'MainActivity' # Defaults to app's launcher | |
152 | + ios: | |
153 | + universal_link: https://app-api.wowenglish.com.cn/app/ | |
154 | + # scene_delegate: true # Defaults to false. | |
155 | + # no_pay: false # Set to false to disable payment. | |
156 | + # ignore_security: true # Set to true to disable security seetings. | ... | ... |