diff --git a/assets/images/ic_customer_service.png b/assets/images/ic_customer_service.png new file mode 100644 index 0000000..195da4a --- /dev/null +++ b/assets/images/ic_customer_service.png diff --git a/lib/generated/json/app_config_entity.g.dart b/lib/generated/json/app_config_entity.g.dart index 82091e3..360f3f9 100644 --- a/lib/generated/json/app_config_entity.g.dart +++ b/lib/generated/json/app_config_entity.g.dart @@ -7,20 +7,37 @@ AppConfigEntity $AppConfigEntityFromJson(Map json) { if (safe != null) { appConfigEntity.safe = safe; } + final List? customerMobileList = + (json['customerMobileList'] as List?) + ?.map((e) => jsonConvert.convert(e) as String) + .toList(); + if (customerMobileList != null) { + appConfigEntity.customerMobileList = customerMobileList; + } + final String? customerQr = jsonConvert.convert(json['customerQr']); + if (customerQr != null) { + appConfigEntity.customerQr = customerQr; + } return appConfigEntity; } Map $AppConfigEntityToJson(AppConfigEntity entity) { final Map data = {}; data['safe'] = entity.safe; + data['customerMobileList'] = entity.customerMobileList; + data['customerQr'] = entity.customerQr; return data; } extension AppConfigEntityExtension on AppConfigEntity { AppConfigEntity copyWith({ String? safe, + List? customerMobileList, + String? customerQr, }) { return AppConfigEntity() - ..safe = safe ?? this.safe; + ..safe = safe ?? this.safe + ..customerMobileList = customerMobileList ?? this.customerMobileList + ..customerQr = customerQr ?? this.customerQr; } } \ No newline at end of file diff --git a/lib/models/app_config_entity.dart b/lib/models/app_config_entity.dart index 97487cc..a7f4d8c 100644 --- a/lib/models/app_config_entity.dart +++ b/lib/models/app_config_entity.dart @@ -6,24 +6,29 @@ import '../generated/json/app_config_entity.g.dart'; @JsonSerializable() class AppConfigEntity { + // 当前是否安全,safe-安全 otherwise-隐藏pay + String? safe; - // 当前是否安全,safe-安全 otherwise-隐藏pay - String? safe; + // 客服手机号列表 + List customerMobileList = []; + // 客服二维码图片地址 + String? customerQr; - AppConfigEntity(); + AppConfigEntity(); - factory AppConfigEntity.fromJson(Map json) => $AppConfigEntityFromJson(json); + factory AppConfigEntity.fromJson(Map json) => + $AppConfigEntityFromJson(json); - Map toJson() => $AppConfigEntityToJson(this); + Map toJson() => $AppConfigEntityToJson(this); - @override - String toString() { - return jsonEncode(this); - } + @override + String toString() { + return jsonEncode(this); + } - // 是否审核中(null或者非"safe"即不安全) - bool isAppReviewing() { - return safe != "safe"; - } + // 是否审核中(null或者非"safe"即不安全) + bool isAppReviewing() { + return safe != "safe"; + } } \ No newline at end of file diff --git a/lib/pages/home/widgets/BaseHomeHeaderWidget.dart b/lib/pages/home/widgets/BaseHomeHeaderWidget.dart index 0db3d8e..28d517d 100644 --- a/lib/pages/home/widgets/BaseHomeHeaderWidget.dart +++ b/lib/pages/home/widgets/BaseHomeHeaderWidget.dart @@ -10,6 +10,7 @@ import '../../../models/course_entity.dart'; import '../../../route/route.dart'; import '../../../utils/image_util.dart'; import '../../user/bloc/user_bloc.dart'; +import 'customer_service_qr_dialog.dart'; typedef HeaderCallback = void Function(dynamic); @@ -80,6 +81,28 @@ class BaseHomeHeaderWidget extends StatelessWidget { textAlign: TextAlign.left, style: TextStyle(color: Colors.white, fontSize: 30.0), )), + FutureBuilder( + future: AppConfigHelper.getAppConfig(), + builder: (context, snapshot) { + final customerQr = snapshot.data?.customerQr?.trim() ?? ''; + if (customerQr.isEmpty) { + return const SizedBox.shrink(); + } + return GestureDetector( + onTap: () => + showCustomerServiceQrDialog(context, customerQr), + child: Padding( + padding: EdgeInsets.only(right: 12.w), + child: Image.asset( + 'ic_customer_service'.assetPng, + width: 90.w, + height: 28.h, + fit: BoxFit.contain, + ), + ), + ); + }, + ), Offstage( offstage: AppConfigHelper.shouldHidePay() || !UserUtil.isLogined(), diff --git a/lib/pages/home/widgets/customer_service_qr_dialog.dart b/lib/pages/home/widgets/customer_service_qr_dialog.dart new file mode 100644 index 0000000..f4a1a60 --- /dev/null +++ b/lib/pages/home/widgets/customer_service_qr_dialog.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +Future showCustomerServiceQrDialog( + BuildContext context, + String imageUrl, +) { + return showDialog( + context: context, + barrierColor: Colors.black54, + barrierDismissible: true, + builder: (context) => Dialog( + backgroundColor: Colors.transparent, + insetPadding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 10.h), + child: ClipRRect( + borderRadius: BorderRadius.circular(12.r), + child: SizedBox( + height: 340.h, + child: AspectRatio( + aspectRatio: 870 / 1110, + child: Image.network( + imageUrl, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + loadingBuilder: (context, child, progress) { + if (progress == null) { + return child; + } + return Container( + color: Colors.white, + alignment: Alignment.center, + child: const CircularProgressIndicator(), + ); + }, + errorBuilder: (context, error, stackTrace) => Container( + color: Colors.white, + alignment: Alignment.center, + child: Text( + '图片加载失败', + style: TextStyle( + color: Colors.grey[600], + fontSize: 14.sp, + ), + ), + ), + ), + ), + ), + ), + ), + ); +} diff --git a/lib/pages/user/user_page.dart b/lib/pages/user/user_page.dart index cd0ff09..f0b7f4a 100644 --- a/lib/pages/user/user_page.dart +++ b/lib/pages/user/user_page.dart @@ -13,6 +13,7 @@ import 'package:wow_english/models/user_entity.dart'; import 'package:wow_english/pages/user/bloc/user_bloc.dart'; import 'package:wow_english/route/route.dart'; import 'package:wow_english/utils/image_util.dart'; +import 'package:wow_english/utils/toast_util.dart'; class UserPage extends StatelessWidget { const UserPage({super.key}); @@ -196,6 +197,16 @@ class _UserView extends StatelessWidget { child: 12.verticalSpace, ), OutlinedButton( + onPressed: () { + _showCustomerServiceDialog(context); + }, + style: normalButtonStyle, + child: Text( + "联系客服", + style: textStyle21sp, + )), + 12.verticalSpace, + OutlinedButton( onPressed: () => pushNamed(AppRouteName.fogPwd), style: normalButtonStyle, child: Text( @@ -232,16 +243,6 @@ class _UserView extends StatelessWidget { 12.verticalSpace, OutlinedButton( onPressed: () { - _showTeacherSelectionDialog(context); - }, - style: normalButtonStyle, - child: Text( - "联系客服", - style: textStyle21sp, - )), - 12.verticalSpace, - OutlinedButton( - onPressed: () { pushNamed(AppRouteName.setting); }, style: normalButtonStyle, @@ -308,7 +309,33 @@ class _UserView extends StatelessWidget { }, ); - void _showTeacherSelectionDialog(BuildContext context) { + Future _showCustomerServiceDialog(BuildContext context) async { + try { + final config = await AppConfigHelper.getAppConfig(); + if (!context.mounted) { + return; + } + + final mobileList = config?.customerMobileList + .map((mobile) => mobile.trim()) + .where((mobile) => mobile.isNotEmpty) + .toSet() + .toList() ?? + []; + if (mobileList.isEmpty) { + showToast('暂无客服联系方式'); + return; + } + _showMobileSelectionDialog(context, mobileList); + } catch (_) { + if (context.mounted) { + showToast('客服联系方式获取失败,请稍后重试'); + } + } + } + + void _showMobileSelectionDialog( + BuildContext context, List mobileList) { showModalBottomSheet( context: context, backgroundColor: Colors.white, @@ -333,30 +360,28 @@ class _UserView extends StatelessWidget { borderRadius: BorderRadius.circular(2), ), ), - // 老师选项列表 + // 客服手机号列表 Flexible( child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, - children: [ - // 恐龙老师选项 - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + children: + List.generate(mobileList.length * 2 - 1, (index) { + if (index.isOdd) { + return const Divider(height: 1); + } + final mobile = mobileList[index ~/ 2]; + return ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), title: Text( - '恐龙老师', + mobile, style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w600, color: Colors.black87, ), ), - subtitle: Text( - '19357119913', - style: TextStyle( - fontSize: 12.sp, - color: Colors.grey[600], - ), - ), trailing: Icon( Icons.phone, color: Colors.green[600], @@ -364,39 +389,10 @@ class _UserView extends StatelessWidget { ), onTap: () { Navigator.pop(context); - _launchPhone('tel:+8619357119913'); + _launchPhone(mobile); }, - ), - const Divider(height: 1), - // Rose老师选项 - ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - title: Text( - 'Rose老师', - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - subtitle: Text( - '19033986279', - style: TextStyle( - fontSize: 12.sp, - color: Colors.grey[600], - ), - ), - trailing: Icon( - Icons.phone, - color: Colors.green[600], - size: 18, - ), - onTap: () { - Navigator.pop(context); - _launchPhone('tel:+8619033986279'); - }, - ), - ], + ); + }), ), ), ), @@ -433,8 +429,9 @@ class _UserView extends StatelessWidget { } void _launchPhone(String phone) async { - if (await canLaunchUrl(Uri.parse(phone))) { - await launchUrl(Uri.parse(phone)); + final phoneUri = Uri(scheme: 'tel', path: phone); + if (await canLaunchUrl(phoneUri)) { + await launchUrl(phoneUri); } else { throw 'Could not phone $phone'; } diff --git a/pubspec.yaml b/pubspec.yaml index 311444a..a990365 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.12+12 +version: 1.0.13+13 environment: sdk: '>=3.2.0 <4.0.0'