Browse Source

上传音乐服务代码

newdev_shunjiawei
liwei1dao 11 months ago
parent
commit
4adce2d7ed
  1. 206
      lib/data/models/goods.dart
  2. 17
      lib/data/models/user_Info.dart
  3. 7
      lib/data/services/ble_manager.dart
  4. 2
      lib/data/services/network/api.dart
  5. 110
      lib/data/services/pay_service.dart
  6. 2
      lib/modules/devices/controllers/devices_controller.dart
  7. 281
      lib/modules/goods/controllers/goods_base_controller.dart
  8. 89
      lib/modules/goods/controllers/goodsai_controller.dart
  9. 81
      lib/modules/goods/controllers/goodsmeet_controller.dart
  10. 81
      lib/modules/goods/controllers/goodstrade_controller.dart
  11. 82
      lib/modules/goods/controllers/goodsvip_controller.dart
  12. 410
      lib/modules/goods/views/goodsai_view.dart
  13. 193
      lib/modules/goods/views/goodsmeet_view.dart
  14. 193
      lib/modules/goods/views/goodstrade_view.dart
  15. 196
      lib/modules/goods/views/goodsvip_view.dart
  16. 159
      local_plugins/music_service/lib/music_service.dart
  17. 157
      local_plugins/music_service/lib/music_service_native.dart
  18. 38
      local_plugins/music_service/lib/music_service_stub.dart

206
lib/data/models/goods.dart

@ -0,0 +1,206 @@
/// 商品类型
/// 0: 普通产品 normal
/// 1: 会员产品 member
// 使用量类型(与后端/Proto枚举对应)
enum UsageType {
/// 未知使用量类型
UsageNull, // 0
/// 翻译
Trade, // 1
/// 会议
Meet, // 2
/// 智能体
AI; // 3
// 解析支持 int 或 string(大小写不敏感)
static UsageType from(dynamic value) {
if (value is int) {
switch (value) {
case 0:
return UsageType.UsageNull;
case 1:
return UsageType.Trade;
case 2:
return UsageType.Meet;
case 3:
return UsageType.AI;
default:
return UsageType.UsageNull;
}
}
if (value is String) {
final v = int.tryParse(value.trim());
if (v != null) return UsageType.from(v);
switch (value.trim().toLowerCase()) {
case 'usagenull':
case 'null':
case 'unknown':
return UsageType.UsageNull;
case 'trade':
return UsageType.Trade;
case 'meet':
case 'meeting':
return UsageType.Meet;
case 'ai':
return UsageType.AI;
default:
return UsageType.UsageNull;
}
}
return UsageType.UsageNull;
}
int get toInt {
switch (this) {
case UsageType.UsageNull:
return 0;
case UsageType.Trade:
return 1;
case UsageType.Meet:
return 2;
case UsageType.AI:
return 3;
}
}
String get name {
switch (this) {
case UsageType.UsageNull:
return 'UsageNull';
case UsageType.Trade:
return 'Trade';
case UsageType.Meet:
return 'Meet';
case UsageType.AI:
return 'AI';
}
}
}
/// 支付商品模型,与后端 DBGoods 对应
/// - id: 产品id
/// - enable: 是否启用
/// - name: 产品名称
/// - desc: 产品描述
/// - price: 产品价格(建议单位:分)
/// - usagetype: 产品类型 0:普通产品 1:会员产品
/// - usagenum: 使用量(时长/次数/额度等)
class DBGoods {
final String id;
final bool enable;
final String name;
final String desc;
final int price; // 建议单位:分
final UsageType usagetype;
final int usagenum;
const DBGoods({
required this.id,
required this.enable,
required this.name,
required this.desc,
required this.price,
required this.usagetype,
required this.usagenum,
});
/// 解析后端 JSON
factory DBGoods.fromJson(Map<String, dynamic> json) {
String _asString(dynamic v) {
if (v == null) return '';
return v is String ? v : v.toString();
}
bool _asBool(dynamic v) {
if (v is bool) return v;
if (v is num) return v != 0;
if (v is String) {
final s = v.trim().toLowerCase();
return s == 'true' || s == '1';
}
return false;
}
int _asInt(dynamic v) {
if (v == null) return 0;
if (v is int) return v;
if (v is num) return v.toInt();
if (v is String) return int.tryParse(v) ?? 0;
return 0;
}
return DBGoods(
id: _asString(json['id']),
enable: _asBool(json['enable']),
name: _asString(json['name']),
desc: _asString(json['desc']),
price: _asInt(json['price']),
usagetype: UsageType.from(json['usagetype']),
usagenum: _asInt(json['usagenum']),
);
}
/// 编码为后端 JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'enable': enable,
'name': name,
'desc': desc,
'price': price,
'usagetype': usagetype.toInt,
'usagenum': usagenum,
};
}
/// 列表解析
static List<DBGoods> listFromJson(dynamic data) {
if (data == null) return const [];
if (data is List) {
return data
.map((e) => DBGoods.fromJson(e as Map<String, dynamic>))
.toList();
}
// 某些接口返回 { list: [...] } 或 { data: [...] }
if (data is Map<String, dynamic>) {
final list = data['list'] ?? data['data'] ?? data['items'];
if (list is List) {
return list
.map((e) => DBGoods.fromJson(e as Map<String, dynamic>))
.toList();
}
}
return const [];
}
/// 价格换算:分 -> 元(仅当 price 单位为分时使用)
double get priceYuan => price / 100.0;
/// 用于支付创建订单的金额(分)
int get payAmountCents => price;
DBGoods copyWith({
String? id,
bool? enable,
String? name,
String? desc,
int? price,
UsageType? usagetype,
int? usagenum,
}) {
return DBGoods(
id: id ?? this.id,
enable: enable ?? this.enable,
name: name ?? this.name,
desc: desc ?? this.desc,
price: price ?? this.price,
usagetype: usagetype ?? this.usagetype,
usagenum: usagenum ?? this.usagenum,
);
}
@override
String toString() {
return 'DBGoods(id: $id, enable: $enable, name: $name, price: $price, usagetype: ${usagetype.name}, usagenum: $usagenum)';
}
}

17
lib/data/models/user_Info.dart

@ -41,7 +41,14 @@ class User {
String phonemac;
String password;
int viplv = 0; //添加会员等级字段,0表示普通用户
// 新增的会员与用量统计字段
int viptime = 0; // 会员到期/剩余时间(秒/毫秒,按后端定义)
int tradetotaltime = 0; // 翻译总时长
int meettotaltime = 0; // 会议总时长
int aichattotaltimer = 0; // 智能体总次数(兼容 aichattotaltime/aichattotaltimer)
int tradetime = 0; // 翻译剩余时长
int meettime = 0; // 会议剩余时长
int aichattimer = 0; // 智能体剩余次数
List<UserDevice> devices;
// 私有构造函数
@ -125,6 +132,14 @@ class User {
'phonemac': phonemac,
'password': password,
'viplv': viplv,
'viptime': viptime,
'tradetotaltime': tradetotaltime,
'meettotaltime': meettotaltime,
// 序列化统一使用 aichattotaltime 键
'aichattotaltime': aichattotaltimer,
'tradetime': tradetime,
'meettime': meettime,
'aichattimer': aichattimer,
'devices': devices.map((device) => device.toJson()).toList(),
},
};

7
lib/data/services/ble_manager.dart

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_storage/get_storage.dart';
@ -692,7 +693,11 @@ class BleManager extends GetxService {
final openaiagentid =
AppConfig.env('OPENAI_AGENT_ID') ?? 'personal_assistant';
final systemPrompt = AppConfig.agentsystemPrompt(openaiagentid);
final musicServiceType = AppConfig.env('APP_MUSICSERVICE_TYPE') ?? 'AUTO';
var musicServiceType =
AppConfig.env('APP_ANDROID_MUSICSERVICE_TYPE') ?? 'AUTO';
if (Platform.isIOS) {
musicServiceType = AppConfig.env('APP_IOS_MUSICSERVICE_TYPE') ?? 'AUTO';
}
final navigationMode =
AppConfig.env('APP_NAVIGATION_MODE') ?? 'external_auto';
// 从Flutter资源包中加载.mcp.json

2
lib/data/services/network/api.dart

@ -221,7 +221,7 @@ class Api {
static getGoods([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/pay_getgoods',
'/api/home/pay_getgoodss',
params: params,
);
}

110
lib/data/services/pay_service.dart

@ -1,6 +1,7 @@
import 'package:get/get.dart';
import 'package:voitrans/data/services/network/api.dart';
import 'package:tobias/tobias.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../modules/login/controllers/weixinLogin.dart';
@ -359,4 +360,113 @@ class PayService extends GetxService {
return null;
}
}
/// 创建 PayPal 支付订单
/// 返回需要打开的支付授权链接(approval_url)或支付页面URL
Future<String?> createPaypalOrder({
required String productId,
required String productName,
String? productDesc,
required int amount,
int paymentMethod = 5, // 约定:5 表示 PayPal
String? clientIp,
String? notifyUrl,
String? extraData,
}) async {
try {
final response = await Api.paycreateorder({
'product_id': productId,
'product_name': productName,
'product_desc': productDesc ?? '',
'amount': amount,
'payment_method': paymentMethod,
'client_ip': clientIp ?? '',
'notify_url': notifyUrl ?? '',
'extra_data': extraData ?? '',
});
// 兼容不同字段命名,尽量从返回中提取可用的支付URL
if (response != null) {
final candidates = [
'paypal_pay_url',
'approval_url',
'paypalApprovalUrl',
'pay_url',
'payment_url',
'url',
];
for (final key in candidates) {
final url = response[key];
if (url is String && url.isNotEmpty) {
return url;
}
}
final data = response['data'];
if (data is Map<String, dynamic>) {
for (final key in candidates) {
final url = data[key];
if (url is String && url.isNotEmpty) {
return url;
}
}
}
}
print('创建 PayPal 订单失败或未返回有效URL: $response');
return null;
} catch (e) {
print('创建 PayPal 订单异常: $e');
return null;
}
}
/// 拉起 PayPal 支付(打开授权链接)。
/// 注意:该方法仅负责跳转至 PayPal 页面,结果需要后端回调通知。
Future<bool> paypalPay({
required String approvalUrl,
PaymentResultCallback? onResult,
}) async {
try {
final uri = Uri.parse(approvalUrl);
final ok = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (ok) {
onResult?.call(true, '已打开 PayPal,请完成支付');
} else {
onResult?.call(false, '无法打开 PayPal 支付页面');
}
return ok;
} catch (e) {
onResult?.call(false, '打开 PayPal 异常: $e');
return false;
}
}
/// 一体化的 PayPal 支付流程:创建订单 -> 打开授权链接
Future<bool> processPaypalPayment({
required String productId,
required int amount,
required String description,
PaymentResultCallback? onResult,
}) async {
try {
final approvalUrl = await createPaypalOrder(
productId: productId,
productName: description,
productDesc: description,
amount: amount,
paymentMethod: 5,
notifyUrl: 'https://api.ideapsound.com/api/home/pay_paypalnotify',
);
if (approvalUrl == null) {
onResult?.call(false, '创建 PayPal 订单失败');
return false;
}
return await paypalPay(approvalUrl: approvalUrl, onResult: onResult);
} catch (e) {
onResult?.call(false, 'PayPal 支付流程异常: $e');
return false;
}
}
}

2
lib/modules/devices/controllers/devices_controller.dart

@ -1,4 +1,4 @@
import 'dart:ffi';
// import 'dart:ffi'; // Not supported on web; not needed here
import 'package:voitrans/data/models/appconfig_model.dart';

281
lib/modules/goods/controllers/goods_base_controller.dart

@ -0,0 +1,281 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../routes/app_routes.dart';
import '../../../data/services/pay_service.dart';
import '../../../data/models/goods.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../../data/services/network/dio_manager.dart';
/// goods 模块基础控制器,封装通用的商品加载与支付流程
abstract class GoodsBaseController extends GetxController {
final RxList<DBGoods> items = <DBGoods>[].obs;
final RxBool loading = false.obs;
final RxString error = ''.obs;
late final PayService _payService = Get.find<PayService>();
@override
void onInit() {
super.onInit();
_init();
}
Future<void> _init() async {
loading.value = true;
error.value = '';
try {
final list = await fetchProducts();
items.assignAll(list);
} catch (e) {
error.value = e.toString();
} finally {
loading.value = false;
}
}
/// 子类实现:返回商品列表(统一使用 DBGoods)
Future<List<DBGoods>> fetchProducts();
/// 统一入口:不指定支付方式时,跳转到支付页面交由用户选择
Future<void> purchase(DBGoods item) async {
final method = await _showPayMethodSheet();
if (method == null) return;
bool ok = false;
String failMsg = '支付失败,请重试';
if (method == _PayMethod.wechat) {
ok = await payWechat(item, onResult: (success, msg) {
if (!success) failMsg = msg ?? failMsg;
});
} else if (method == _PayMethod.alipay) {
ok = await payAlipay(item, onResult: (success, msg) {
if (!success) failMsg = msg ?? failMsg;
});
} else if (method == _PayMethod.paypal) {
ok = await payPaypal(item, onResult: (success, msg) {
if (!success) failMsg = msg ?? failMsg;
});
}
if (!ok) {
Get.snackbar('提示', failMsg,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(12));
}
}
/// 底部弹窗:选择支付方式
Future<_PayMethod?> _showPayMethodSheet() async {
final context = Get.context;
final isDark =
context != null && Theme.of(context).brightness == Brightness.dark;
return await Get.bottomSheet<_PayMethod>(
SafeArea(
child: Container(
decoration: BoxDecoration(
color: isDark ? Colors.grey[900] : Colors.white,
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: isDark ? Colors.white24 : Colors.black12,
borderRadius: BorderRadius.circular(2),
),
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'选择支付方式',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isDark ? Colors.white : Colors.black,
),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () => Get.back(result: _PayMethod.wechat),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
isDark ? Colors.white10 : Colors.black12,
border: Border.all(
color: isDark
? Colors.white24
: Colors.black12,
),
),
child: Icon(Icons.wechat,
color: Colors.green[600]),
),
const SizedBox(height: 8),
Text(
'微信支付',
style: TextStyle(
fontSize: 13,
color: isDark ? Colors.white : Colors.black,
),
),
],
),
),
GestureDetector(
onTap: () => Get.back(result: _PayMethod.alipay),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
isDark ? Colors.white10 : Colors.black12,
border: Border.all(
color: isDark
? Colors.white24
: Colors.black12,
),
),
child: Icon(Icons.payment,
color: Colors.blue[600]),
),
const SizedBox(height: 8),
Text(
'支付宝',
style: TextStyle(
fontSize: 13,
color: isDark ? Colors.white : Colors.black,
),
),
],
),
),
GestureDetector(
onTap: () => Get.back(result: _PayMethod.paypal),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
isDark ? Colors.white10 : Colors.black12,
border: Border.all(
color: isDark
? Colors.white24
: Colors.black12,
),
),
child: Icon(
Icons.account_balance_wallet,
color: Colors.indigo[600],
),
),
const SizedBox(height: 8),
Text(
'PayPal',
style: TextStyle(
fontSize: 13,
color: isDark ? Colors.white : Colors.black,
),
),
],
),
),
],
),
const SizedBox(height: 12),
],
),
),
],
),
),
),
isScrollControlled: false,
ignoreSafeArea: false,
backgroundColor: Colors.transparent,
);
}
/// 指定微信支付(直接下单并拉起微信)
Future<bool> payWechat(DBGoods item,
{PaymentResultCallback? onResult}) async {
final cents = item.price; // DBGoods.price 单位:分
final serverUrl = dotenv.env['SERVER_URL'] ?? DioManager().baseUrl;
final paymentData = await _payService.createWechatPayOrder(
productId: item.id,
productName: item.name,
productDesc: item.desc,
amount: cents,
notifyUrl: '$serverUrl/api/home/pay_wechatnotify',
);
if (paymentData == null) {
onResult?.call(false, '创建微信订单失败');
return false;
}
return _payService.wechatPay(paymentData: paymentData, onResult: onResult);
}
/// 指定支付宝支付(直接下单并拉起支付宝)
Future<bool> payAlipay(DBGoods item,
{PaymentResultCallback? onResult}) async {
final cents = item.price; // DBGoods.price 单位:分
final orderInfo = await _payService.createAlipayOrder(
productId: item.id,
productName: item.name,
productDesc: item.desc,
amount: cents,
notifyUrl: 'https://api.ideapsound.com/api/home/pay_alipaynotify',
);
if (orderInfo == null) {
onResult?.call(false, '创建支付宝订单失败');
return false;
}
return _payService.alipay(orderInfo: orderInfo, onResult: onResult);
}
/// 指定 PayPal 支付(创建订单并打开授权链接)
Future<bool> payPaypal(DBGoods item,
{PaymentResultCallback? onResult}) async {
final cents = item.price; // DBGoods.price 单位:分
final approvalUrl = await _payService.createPaypalOrder(
productId: item.id,
productName: item.name,
productDesc: item.desc,
amount: cents,
notifyUrl: 'https://api.ideapsound.com/api/home/pay_paypalnotify',
);
if (approvalUrl == null) {
onResult?.call(false, '创建 PayPal 订单失败');
return false;
}
return _payService.paypalPay(approvalUrl: approvalUrl, onResult: onResult);
}
}
enum _PayMethod { wechat, alipay, paypal }

89
lib/modules/goods/controllers/goodsai_controller.dart

@ -1,65 +1,40 @@
import 'dart:convert';
import 'dart:developer' as developer;
import 'package:get/get.dart';
import '../../../routes/app_routes.dart';
import '../../../data/models/goods.dart';
import '../../../data/services/network/api.dart';
import 'goods_base_controller.dart';
// ... existing code ...
class AIItem {
final String id;
final String title;
final String description;
final double price;
final String productId;
AIItem({
required this.id,
required this.title,
required this.description,
required this.price,
required this.productId,
});
}
class GoodsaiController extends GetxController {
final RxList<AIItem> items = <AIItem>[].obs;
final RxBool loading = false.obs;
final RxString error = ''.obs;
class GoodsaiController extends GoodsBaseController {
@override
void onInit() {
super.onInit();
_loadProducts();
}
Future<void> _loadProducts() async {
loading.value = true;
error.value = '';
Future<List<DBGoods>> fetchProducts() async {
final resp = await Api.getGoods({'usagetype': UsageType.AI.toInt});
// 输出完整响应,便于调试接口结构
try {
items.assignAll([
AIItem(
id: 'ai_chat',
title: '通用AI聊天包',
description: '更长对话额度与工具调用',
price: 19.9,
productId: 'ai_chat_101',
),
AIItem(
id: 'ai_tools',
title: 'AI工具扩展',
description: '额外工具能力与上下文长度',
price: 39.9,
productId: 'ai_tools_201',
),
]);
} catch (e) {
error.value = e.toString();
} finally {
loading.value = false;
developer.log('getGoods(AI) resp: ${jsonEncode(resp)}');
} catch (_) {
developer.log('getGoods(AI) resp (raw): $resp');
}
if (resp == null || resp is! Map) return [];
if (resp.containsKey('error') || resp.containsKey('msg')) {
final msg = resp['error'] ?? resp['msg'] ?? '服务器错误';
throw Exception(msg);
}
final container =
(resp is Map && resp['data'] is Map) ? resp['data'] : resp;
final data = (container is Map) ? (container['goodss']) : null;
if (data is List) {
final list =
data.whereType<Map<String, dynamic>>().map(DBGoods.fromJson).toList();
// 兜底过滤确保展示 AI 类型
return list.where((g) => g.usagetype == UsageType.AI).toList();
}
return [];
}
void purchase(AIItem item) {
Get.toNamed(Routes.payment, arguments: {
'productId': item.productId,
'amount': item.price,
'description': item.title,
});
}
}
// 如需自定义跳转逻辑,可保留重写调用父类
// Future<void> purchase(DBGoods item) async => super.purchase(item);
}

81
lib/modules/goods/controllers/goodsmeet_controller.dart

@ -1,65 +1,30 @@
import 'package:get/get.dart';
import '../../../routes/app_routes.dart';
import '../../../data/models/goods.dart';
import '../../../data/services/network/api.dart';
import 'goods_base_controller.dart';
class MeetItem {
final String id;
final String title;
final String description;
final double price;
final String productId;
MeetItem({
required this.id,
required this.title,
required this.description,
required this.price,
required this.productId,
});
}
class GoodsmeetController extends GetxController {
final RxList<MeetItem> items = <MeetItem>[].obs;
final RxBool loading = false.obs;
final RxString error = ''.obs;
class GoodsmeetController extends GoodsBaseController {
@override
void onInit() {
super.onInit();
_loadProducts();
}
Future<void> _loadProducts() async {
loading.value = true;
error.value = '';
try {
items.assignAll([
MeetItem(
id: 'meet_basic',
title: '会议助手基础版',
description: '会议纪要与基础翻译',
price: 29.9,
productId: 'meet_101',
),
MeetItem(
id: 'meet_pro',
title: '会议助手高级版',
description: '纪要、翻译与AI总结增强',
price: 59.9,
productId: 'meet_201',
),
]);
} catch (e) {
error.value = e.toString();
} finally {
loading.value = false;
Future<List<DBGoods>> fetchProducts() async {
final resp = await Api.getGoods({'usagetype': UsageType.Meet.toInt});
if (resp == null || resp is! Map) return [];
if (resp.containsKey('error') || resp.containsKey('msg')) {
final msg = resp['error'] ?? resp['msg'] ?? '服务器错误';
throw Exception(msg);
}
final container =
(resp is Map && resp['data'] is Map) ? resp['data'] : resp;
final data = (container is Map)
? (container['goodss'] ?? container['goodes'] ?? container['goods'])
: null;
if (data is List) {
final list =
data.whereType<Map<String, dynamic>>().map(DBGoods.fromJson).toList();
return list.where((g) => g.usagetype == UsageType.Meet).toList();
}
return [];
}
void purchase(MeetItem item) {
Get.toNamed(Routes.payment, arguments: {
'productId': item.productId,
'amount': item.price,
'description': item.title,
});
}
}
// Future<void> purchase(DBGoods item) async => super.purchase(item);
}

81
lib/modules/goods/controllers/goodstrade_controller.dart

@ -1,65 +1,30 @@
import 'package:get/get.dart';
import '../../../routes/app_routes.dart';
import '../../../data/models/goods.dart';
import '../../../data/services/network/api.dart';
import 'goods_base_controller.dart';
class TradeItem {
final String id;
final String title;
final String description;
final double price;
final String productId;
TradeItem({
required this.id,
required this.title,
required this.description,
required this.price,
required this.productId,
});
}
class GoodstradeController extends GetxController {
final RxList<TradeItem> items = <TradeItem>[].obs;
final RxBool loading = false.obs;
final RxString error = ''.obs;
class GoodstradeController extends GoodsBaseController {
@override
void onInit() {
super.onInit();
_loadProducts();
}
Future<void> _loadProducts() async {
loading.value = true;
error.value = '';
try {
items.assignAll([
TradeItem(
id: 'trans_100',
title: '翻译包 100 分钟',
description: '实时翻译服务时长包',
price: 19.9,
productId: 'trans_100',
),
TradeItem(
id: 'trans_500',
title: '翻译包 500 分钟',
description: '更划算的翻译时长包',
price: 79.9,
productId: 'trans_500',
),
]);
} catch (e) {
error.value = e.toString();
} finally {
loading.value = false;
Future<List<DBGoods>> fetchProducts() async {
final resp = await Api.getGoods({'usagetype': UsageType.Trade.toInt});
if (resp == null || resp is! Map) return [];
if (resp.containsKey('error') || resp.containsKey('msg')) {
final msg = resp['error'] ?? resp['msg'] ?? '服务器错误';
throw Exception(msg);
}
final container =
(resp is Map && resp['data'] is Map) ? resp['data'] : resp;
final data = (container is Map)
? (container['goodss'] ?? container['goodes'] ?? container['goods'])
: null;
if (data is List) {
final list =
data.whereType<Map<String, dynamic>>().map(DBGoods.fromJson).toList();
return list.where((g) => g.usagetype == UsageType.Trade).toList();
}
return [];
}
void purchase(TradeItem item) {
Get.toNamed(Routes.payment, arguments: {
'productId': item.productId,
'amount': item.price,
'description': item.title,
});
}
}
// Future<void> purchase(DBGoods item) async => super.purchase(item);
}

82
lib/modules/goods/controllers/goodsvip_controller.dart

@ -1,66 +1,30 @@
import 'package:get/get.dart';
import '../../../routes/app_routes.dart';
import '../../../data/models/goods.dart';
import '../../../data/services/network/api.dart';
import 'goods_base_controller.dart';
class GoodsItem {
final String id;
final String title;
final String description;
final double price;
final String productId;
GoodsItem({
required this.id,
required this.title,
required this.description,
required this.price,
required this.productId,
});
}
class GoodsvipController extends GetxController {
final RxList<GoodsItem> items = <GoodsItem>[].obs;
final RxBool loading = false.obs;
final RxString error = ''.obs;
class GoodsvipController extends GoodsBaseController {
@override
void onInit() {
super.onInit();
_loadProducts();
}
Future<void> _loadProducts() async {
loading.value = true;
error.value = '';
try {
// 示例数据,可接后端商品接口替换
items.assignAll([
GoodsItem(
id: 'vip_month',
title: '月度会员',
description: '解锁高级功能,按月订阅',
price: 9.9,
productId: 'vip_101',
),
GoodsItem(
id: 'vip_year',
title: '年度会员',
description: '解锁高级功能,按年订阅',
price: 99.0,
productId: 'vip_201',
),
]);
} catch (e) {
error.value = e.toString();
} finally {
loading.value = false;
Future<List<DBGoods>> fetchProducts() async {
final resp = await Api.getGoods({'usagetype': UsageType.UsageNull.toInt});
if (resp == null || resp is! Map) return [];
if (resp.containsKey('error') || resp.containsKey('msg')) {
final msg = resp['error'] ?? resp['msg'] ?? '服务器错误';
throw Exception(msg);
}
final container =
(resp is Map && resp['data'] is Map) ? resp['data'] : resp;
final data = (container is Map)
? (container['goodss'] ?? container['goodes'] ?? container['goods'])
: null;
if (data is List) {
final list =
data.whereType<Map<String, dynamic>>().map(DBGoods.fromJson).toList();
return list.where((g) => g.usagetype == UsageType.UsageNull).toList();
}
return [];
}
void purchase(GoodsItem item) {
Get.toNamed(Routes.payment, arguments: {
'productId': item.productId,
'amount': item.price,
'description': item.title,
});
}
}
// Future<void> purchase(DBGoods item) async => super.purchase(item);
}

410
lib/modules/goods/views/goodsai_view.dart

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../data/models/goods.dart';
import '../../../data/models/user_Info.dart';
import '../controllers/goodsai_controller.dart';
class GoodsaiView extends GetView<GoodsaiController> {
@ -32,20 +34,394 @@ class GoodsaiView extends GetView<GoodsaiController> {
if (controller.error.isNotEmpty) {
return Center(child: Text(controller.error.value));
}
return ListView.separated(
// 用户用量信息
int totalAi = 0;
int remainAi = 0;
if (User.isLoggedIn()) {
final u = User.instance;
totalAi = u.aichattotaltimer;
remainAi = u.aichattimer;
}
final usedAi =
(totalAi > 0) ? (totalAi - remainAi).clamp(0, totalAi) : 0;
final progress = (totalAi > 0) ? usedAi / totalAi : 0.0;
return ListView(
padding: EdgeInsets.all(16.w),
itemCount: controller.items.length,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (_, index) {
final item = controller.items[index];
return _buildCard(item, isDarkMode);
},
children: [
_buildHeader(isDarkMode),
SizedBox(height: 12.h),
_buildUsageCard(isDarkMode,
totalAi: totalAi, remainAi: remainAi, progress: progress),
SizedBox(height: 16.h),
Text(
'可购买套餐',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
SizedBox(height: 8.h),
...controller.items.map((item) => Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: _buildCard(item, isDarkMode),
)),
SizedBox(height: 16.h),
_buildFeaturesSection(isDarkMode),
SizedBox(height: 16.h),
_buildFooterSection(isDarkMode),
],
);
}),
);
}
Widget _buildCard(AIItem item, bool isDarkMode) {
Widget _buildHeader(bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.indigo[900]!.withOpacity(0.3)
: Colors.indigo[100]!,
borderRadius: BorderRadius.circular(22.r),
),
child: Icon(Icons.smart_toy,
color: isDarkMode ? Colors.indigo[300]! : Colors.indigo[600]!),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'AI 功能',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w700,
color: isDarkMode ? Colors.white : Colors.black,
),
),
SizedBox(height: 6.h),
Text(
'更长对话额度、更多工具调用、更强上下文长度支持。',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
SizedBox(height: 4.h),
Text(
'适用于智能体问答、总结分析、任务执行等场景。',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
],
),
),
],
),
);
}
Widget _buildUsageCard(
bool isDarkMode, {
required int totalAi,
required int remainAi,
required double progress,
}) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.timeline,
color: isDarkMode ? Colors.blue[300]! : Colors.blue[600]!),
SizedBox(width: 8.w),
Text(
'使用进度',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
],
),
SizedBox(height: 12.h),
LinearProgressIndicator(
value: progress,
minHeight: 8.h,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[300],
valueColor: AlwaysStoppedAnimation<Color>(
isDarkMode ? Colors.blue[300]! : Colors.blue[600]!),
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'总次数:$totalAi',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
Text(
'剩余:$remainAi',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
],
),
],
),
);
}
Widget _buildFeaturesSection(bool isDarkMode) {
final textColor = isDarkMode ? Colors.white : Colors.black;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'功能说明',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: textColor,
),
),
SizedBox(height: 10.h),
Wrap(
spacing: 12.w,
runSpacing: 12.h,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
children: [
_buildFeatureCard(
isDarkMode,
icon: Icons.music_note,
title: '听歌与推荐',
lines: [
'检索曲库、播放控制',
'智能推荐歌单与相似歌曲',
],
colorSeed: Colors.purple,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.navigation,
title: '导航与路线',
lines: [
'路线规划与拥堵规避',
'语音播报与途径点设置',
],
colorSeed: Colors.orange,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.wb_sunny,
title: '天气查询',
lines: [
'实时天气与预报',
'穿衣建议与出行提醒',
],
colorSeed: Colors.blue,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.place,
title: '附近搜索',
lines: [
'餐饮/加油/停车等地点检索',
'评分、营业时间与路线直达',
],
colorSeed: Colors.green,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.newspaper,
title: '新闻资讯',
lines: [
'热点快讯与专题速览',
'长文总结与要点提炼',
],
colorSeed: Colors.indigo,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.call,
title: '通讯助手',
lines: [
'帮忙拨打电话与发短信',
'模板消息与日程提醒',
],
colorSeed: Colors.teal,
),
],
),
SizedBox(height: 12.h),
Text(
'以上功能受设备权限、服务可用性与网络状况影响,具体以实际表现为准。',
style: TextStyle(fontSize: 12.sp, color: subColor),
),
],
);
}
Widget _buildFooterSection(bool isDarkMode) {
final subColor = isDarkMode ? Colors.white60 : Colors.grey[600];
return Padding(
padding: EdgeInsets.symmetric(vertical: 12.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: double.infinity,
child: Divider(color: isDarkMode ? Colors.white24 : Colors.black12),
),
SizedBox(height: 8.h),
Text(
'应用名称:VoiTrans · 服务提供:VoiTrans',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
SizedBox(height: 4.h),
Text(
'更多信息请前往 设置 > 关于 查看',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
SizedBox(height: 4.h),
Text(
'© 2025 VoiTrans',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
],
),
);
}
Widget _buildFeatureCard(
bool isDarkMode, {
required IconData icon,
required String title,
required List<String> lines,
required MaterialColor colorSeed,
}) {
final bgColor = isDarkMode ? Colors.grey[900] : Colors.white;
final mainColor = isDarkMode ? colorSeed[300]! : colorSeed[600]!;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
final width = (Get.width - 16.w * 2 - 12.w) / 2;
return Container(
width: width,
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 36.w,
height: 36.w,
decoration: BoxDecoration(
color: isDarkMode
? mainColor.withOpacity(0.25)
: mainColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(18.r),
),
child: Icon(icon, color: mainColor),
),
SizedBox(width: 8.w),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
],
),
SizedBox(height: 8.h),
...lines.map((line) => Padding(
padding: EdgeInsets.only(bottom: 4.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.check_circle, color: mainColor, size: 14.sp),
SizedBox(width: 6.w),
Expanded(
child: Text(
line,
style: TextStyle(fontSize: 12.sp, color: subColor),
),
),
],
),
)),
],
),
);
}
Widget _buildCard(DBGoods item, bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
@ -73,26 +449,24 @@ class GoodsaiView extends GetView<GoodsaiController> {
borderRadius: BorderRadius.circular(21.r),
),
child: Icon(Icons.auto_awesome,
color:
isDarkMode ? Colors.teal[300]! : Colors.teal[600]!),
color: isDarkMode ? Colors.teal[300]! : Colors.teal[600]!),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title,
Text(item.name,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
)),
SizedBox(height: 4.h),
Text(item.description,
Text(item.desc,
style: TextStyle(
fontSize: 12.sp,
color:
isDarkMode ? Colors.white70 : Colors.grey[600],
color: isDarkMode ? Colors.white70 : Colors.grey[600],
)),
],
),
@ -100,7 +474,7 @@ class GoodsaiView extends GetView<GoodsaiController> {
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('¥${item.price.toStringAsFixed(2)}',
Text('¥${item.priceYuan.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
@ -110,8 +484,8 @@ class GoodsaiView extends GetView<GoodsaiController> {
GestureDetector(
onTap: () => controller.purchase(item),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w, vertical: 6.h),
padding:
EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(20.r),
@ -128,4 +502,4 @@ class GoodsaiView extends GetView<GoodsaiController> {
),
);
}
}
}

193
lib/modules/goods/views/goodsmeet_view.dart

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:voitrans/data/models/goods.dart';
import '../controllers/goodsmeet_controller.dart';
class GoodsmeetView extends GetView<GoodsmeetController> {
@ -34,18 +35,24 @@ class GoodsmeetView extends GetView<GoodsmeetController> {
}
return ListView.separated(
padding: EdgeInsets.all(16.w),
itemCount: controller.items.length,
itemCount: controller.items.length + 2,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (_, index) {
final item = controller.items[index];
return _buildCard(item, isDarkMode);
if (index < controller.items.length) {
final item = controller.items[index];
return _buildCard(item, isDarkMode);
}
if (index == controller.items.length) {
return _buildFeaturesSection(isDarkMode);
}
return _buildFooterSection(isDarkMode);
},
);
}),
);
}
Widget _buildCard(MeetItem item, bool isDarkMode) {
Widget _buildCard(DBGoods item, bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
@ -73,27 +80,24 @@ class GoodsmeetView extends GetView<GoodsmeetController> {
borderRadius: BorderRadius.circular(21.r),
),
child: Icon(Icons.people_alt,
color: isDarkMode
? Colors.pink[300]!
: Colors.pink[600]!),
color: isDarkMode ? Colors.pink[300]! : Colors.pink[600]!),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title,
Text(item.name,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
)),
SizedBox(height: 4.h),
Text(item.description,
Text(item.desc,
style: TextStyle(
fontSize: 12.sp,
color:
isDarkMode ? Colors.white70 : Colors.grey[600],
color: isDarkMode ? Colors.white70 : Colors.grey[600],
)),
],
),
@ -111,8 +115,8 @@ class GoodsmeetView extends GetView<GoodsmeetController> {
GestureDetector(
onTap: () => controller.purchase(item),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w, vertical: 6.h),
padding:
EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(20.r),
@ -129,4 +133,165 @@ class GoodsmeetView extends GetView<GoodsmeetController> {
),
);
}
}
Widget _buildFeaturesSection(bool isDarkMode) {
final textColor = isDarkMode ? Colors.white : Colors.black;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'功能说明',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: textColor,
),
),
SizedBox(height: 10.h),
Wrap(
spacing: 12.w,
runSpacing: 12.h,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
children: [
_buildFeatureCard(
isDarkMode,
icon: Icons.mic,
title: '会议记录',
lines: ['自动录音与分段', '讲话人标注'],
colorSeed: Colors.pink,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.summarize,
title: '转写与总结',
lines: ['长音频转写', '关键要点提炼'],
colorSeed: Colors.blue,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.search,
title: '关键词检索',
lines: ['按主题/人名检索', '时间线跳转'],
colorSeed: Colors.indigo,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.share,
title: '协同分享',
lines: ['导出纪要', '多平台分享'],
colorSeed: Colors.teal,
),
],
),
SizedBox(height: 12.h),
Text(
'建议在安静环境使用,效果受麦克风与网络影响,具体以实际表现为准。',
style: TextStyle(fontSize: 12.sp, color: subColor),
),
],
);
}
Widget _buildFeatureCard(
bool isDarkMode, {
required IconData icon,
required String title,
required List<String> lines,
required MaterialColor colorSeed,
}) {
final bgColor = isDarkMode ? Colors.grey[900] : Colors.white;
final mainColor = isDarkMode ? colorSeed[300]! : colorSeed[600]!;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
final width = (Get.width - 16.w * 2 - 12.w) / 2;
return Container(
width: width,
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 36.w,
height: 36.w,
decoration: BoxDecoration(
color: isDarkMode
? mainColor.withOpacity(0.25)
: mainColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(18.r),
),
child: Icon(icon, color: mainColor),
),
SizedBox(width: 8.w),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
),
],
),
SizedBox(height: 8.h),
...lines.map((l) => Padding(
padding: EdgeInsets.only(bottom: 4.h),
child:
Text(l, style: TextStyle(fontSize: 12.sp, color: subColor)),
)),
],
),
);
}
Widget _buildFooterSection(bool isDarkMode) {
final subColor = isDarkMode ? Colors.white60 : Colors.grey[600];
return Padding(
padding: EdgeInsets.symmetric(vertical: 12.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: double.infinity,
child: Divider(color: isDarkMode ? Colors.white24 : Colors.black12),
),
SizedBox(height: 8.h),
Text(
'应用名称:VoiTrans · 服务提供:VoiTrans',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
SizedBox(height: 4.h),
Text(
'更多信息请前往 设置 > 关于 查看',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
SizedBox(height: 4.h),
Text(
'© 2025 VoiTrans',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
],
),
);
}
}

193
lib/modules/goods/views/goodstrade_view.dart

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:voitrans/data/models/goods.dart';
import '../controllers/goodstrade_controller.dart';
class GoodstradeView extends GetView<GoodstradeController> {
@ -34,18 +35,24 @@ class GoodstradeView extends GetView<GoodstradeController> {
}
return ListView.separated(
padding: EdgeInsets.all(16.w),
itemCount: controller.items.length,
itemCount: controller.items.length + 2,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (_, index) {
final item = controller.items[index];
return _buildCard(item, isDarkMode);
if (index < controller.items.length) {
final item = controller.items[index];
return _buildCard(item, isDarkMode);
}
if (index == controller.items.length) {
return _buildFeaturesSection(isDarkMode);
}
return _buildFooterSection(isDarkMode);
},
);
}),
);
}
Widget _buildCard(TradeItem item, bool isDarkMode) {
Widget _buildCard(DBGoods item, bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
@ -73,27 +80,24 @@ class GoodstradeView extends GetView<GoodstradeController> {
borderRadius: BorderRadius.circular(21.r),
),
child: Icon(Icons.translate,
color: isDarkMode
? Colors.indigo[300]!
: Colors.indigo[600]!),
color: isDarkMode ? Colors.indigo[300]! : Colors.indigo[600]!),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title,
Text(item.name,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
)),
SizedBox(height: 4.h),
Text(item.description,
Text(item.desc,
style: TextStyle(
fontSize: 12.sp,
color:
isDarkMode ? Colors.white70 : Colors.grey[600],
color: isDarkMode ? Colors.white70 : Colors.grey[600],
)),
],
),
@ -111,8 +115,8 @@ class GoodstradeView extends GetView<GoodstradeController> {
GestureDetector(
onTap: () => controller.purchase(item),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w, vertical: 6.h),
padding:
EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(20.r),
@ -129,4 +133,165 @@ class GoodstradeView extends GetView<GoodstradeController> {
),
);
}
}
Widget _buildFeaturesSection(bool isDarkMode) {
final textColor = isDarkMode ? Colors.white : Colors.black;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'功能说明',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: textColor,
),
),
SizedBox(height: 10.h),
Wrap(
spacing: 12.w,
runSpacing: 12.h,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
children: [
_buildFeatureCard(
isDarkMode,
icon: Icons.translate,
title: '实时同传',
lines: ['中英互译', '低延时语音播报'],
colorSeed: Colors.indigo,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.text_snippet,
title: '文本翻译',
lines: ['多语种文本翻译', '术语优化与语境保持'],
colorSeed: Colors.blue,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.library_books,
title: '场景词库',
lines: ['旅游/商务/生活词库', '专属词汇记忆'],
colorSeed: Colors.teal,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.subtitles,
title: '字幕显示',
lines: ['对话字幕/大字模式', '颜色高对比度'],
colorSeed: Colors.purple,
),
],
),
SizedBox(height: 12.h),
Text(
'翻译效果受麦克风、网络时延与环境噪声影响,具体以实际表现为准。',
style: TextStyle(fontSize: 12.sp, color: subColor),
),
],
);
}
Widget _buildFeatureCard(
bool isDarkMode, {
required IconData icon,
required String title,
required List<String> lines,
required MaterialColor colorSeed,
}) {
final bgColor = isDarkMode ? Colors.grey[900] : Colors.white;
final mainColor = isDarkMode ? colorSeed[300]! : colorSeed[600]!;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
final width = (Get.width - 16.w * 2 - 12.w) / 2;
return Container(
width: width,
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 36.w,
height: 36.w,
decoration: BoxDecoration(
color: isDarkMode
? mainColor.withOpacity(0.25)
: mainColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(18.r),
),
child: Icon(icon, color: mainColor),
),
SizedBox(width: 8.w),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
),
],
),
SizedBox(height: 8.h),
...lines.map((l) => Padding(
padding: EdgeInsets.only(bottom: 4.h),
child:
Text(l, style: TextStyle(fontSize: 12.sp, color: subColor)),
)),
],
),
);
}
Widget _buildFooterSection(bool isDarkMode) {
final subColor = isDarkMode ? Colors.white60 : Colors.grey[600];
return Padding(
padding: EdgeInsets.symmetric(vertical: 12.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: double.infinity,
child: Divider(color: isDarkMode ? Colors.white24 : Colors.black12),
),
SizedBox(height: 8.h),
Text(
'应用名称:VoiTrans · 服务提供:VoiTrans',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
SizedBox(height: 4.h),
Text(
'更多信息请前往 设置 > 关于 查看',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
SizedBox(height: 4.h),
Text(
'© 2025 VoiTrans',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
],
),
);
}
}

196
lib/modules/goods/views/goodsvip_view.dart

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:voitrans/data/models/goods.dart';
import '../controllers/goodsvip_controller.dart';
class GoodsvipView extends GetView<GoodsvipController> {
@ -34,19 +35,24 @@ class GoodsvipView extends GetView<GoodsvipController> {
}
return ListView.separated(
padding: EdgeInsets.all(16.w),
itemCount: controller.items.length,
separatorBuilder: (_, __) =>
SizedBox(height: 12.h),
itemCount: controller.items.length + 2,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (_, index) {
final item = controller.items[index];
return _buildGoodsCard(item, isDarkMode);
if (index < controller.items.length) {
final item = controller.items[index];
return _buildGoodsCard(item, isDarkMode);
}
if (index == controller.items.length) {
return _buildFeaturesSection(isDarkMode);
}
return _buildFooterSection(isDarkMode);
},
);
}),
);
}
Widget _buildGoodsCard(GoodsItem item, bool isDarkMode) {
Widget _buildGoodsCard(DBGoods item, bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
@ -74,27 +80,24 @@ class GoodsvipView extends GetView<GoodsvipController> {
borderRadius: BorderRadius.circular(21.r),
),
child: Icon(Icons.workspace_premium,
color: isDarkMode
? Colors.amber[300]!
: Colors.amber[600]!),
color: isDarkMode ? Colors.amber[300]! : Colors.amber[600]!),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title,
Text(item.name,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
)),
SizedBox(height: 4.h),
Text(item.description,
Text(item.desc,
style: TextStyle(
fontSize: 12.sp,
color:
isDarkMode ? Colors.white70 : Colors.grey[600],
color: isDarkMode ? Colors.white70 : Colors.grey[600],
)),
],
),
@ -113,8 +116,8 @@ class GoodsvipView extends GetView<GoodsvipController> {
GestureDetector(
onTap: () => controller.purchase(item),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w, vertical: 6.h),
padding:
EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: Colors.blue[600],
borderRadius: BorderRadius.circular(20.r),
@ -134,4 +137,165 @@ class GoodsvipView extends GetView<GoodsvipController> {
),
);
}
}
Widget _buildFeaturesSection(bool isDarkMode) {
final textColor = isDarkMode ? Colors.white : Colors.black;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'功能说明',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: textColor,
),
),
SizedBox(height: 10.h),
Wrap(
spacing: 12.w,
runSpacing: 12.h,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
children: [
_buildFeatureCard(
isDarkMode,
icon: Icons.workspace_premium,
title: '权益总览',
lines: ['更高使用额度', '更低单价'],
colorSeed: Colors.amber,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.support_agent,
title: '优先服务',
lines: ['专属客服通道', '问题快速响应'],
colorSeed: Colors.purple,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.local_offer,
title: '活动与折扣',
lines: ['会员专属活动', '长期优惠计划'],
colorSeed: Colors.indigo,
),
_buildFeatureCard(
isDarkMode,
icon: Icons.auto_awesome,
title: '通用特权',
lines: ['新功能优先体验', '跨设备同步'],
colorSeed: Colors.teal,
),
],
),
SizedBox(height: 12.h),
Text(
'具体权益以实际套餐说明为准,可能随版本迭代调整。',
style: TextStyle(fontSize: 12.sp, color: subColor),
),
],
);
}
Widget _buildFeatureCard(
bool isDarkMode, {
required IconData icon,
required String title,
required List<String> lines,
required MaterialColor colorSeed,
}) {
final bgColor = isDarkMode ? Colors.grey[900] : Colors.white;
final mainColor = isDarkMode ? colorSeed[300]! : colorSeed[600]!;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
final width = (Get.width - 16.w * 2 - 12.w) / 2;
return Container(
width: width,
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 36.w,
height: 36.w,
decoration: BoxDecoration(
color: isDarkMode
? mainColor.withOpacity(0.25)
: mainColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(18.r),
),
child: Icon(icon, color: mainColor),
),
SizedBox(width: 8.w),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
),
],
),
SizedBox(height: 8.h),
...lines.map((l) => Padding(
padding: EdgeInsets.only(bottom: 4.h),
child: Text(l, style: TextStyle(fontSize: 12.sp, color: subColor)),
)),
],
),
);
}
Widget _buildFooterSection(bool isDarkMode) {
final subColor = isDarkMode ? Colors.white60 : Colors.grey[600];
return Padding(
padding: EdgeInsets.symmetric(vertical: 12.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: double.infinity,
child:
Divider(color: isDarkMode ? Colors.white24 : Colors.black12),
),
SizedBox(height: 8.h),
Text(
'应用名称:VoiTrans · 服务提供:VoiTrans',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
SizedBox(height: 4.h),
Text(
'更多信息请前往 设置 > 关于 查看',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
SizedBox(height: 4.h),
Text(
'© 2025 VoiTrans',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11.sp, color: subColor),
),
],
),
);
}
}

159
local_plugins/music_service/lib/music_service.dart

@ -1,157 +1,2 @@
import 'dart:ffi';
import 'package:flutter/services.dart';
class MusicService {
static final MusicService _instance = MusicService._internal();
/// 获取单例
factory MusicService() => _instance;
// 方法通道
final MethodChannel _methodChannel = const MethodChannel(
'com.yunqiinnovation.music_service',
);
// 事件通道 - 简化为只使用数据和状态通道
late final EventChannel _dataEventChannel;
late final EventChannel _statusEventChannel;
// 事件流
late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream;
// 获取事件流
Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> get onStatusUpdated => _statusStream;
// 内部构造函数
MusicService._internal() {
_dataEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/data_events',
);
_statusEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/status_events',
);
// 初始化事件流
_dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.receiveBroadcastStream();
}
Future<void> playMusic(Map<String, dynamic> song) async {
await _methodChannel.invokeMethod('play', {'song': song});
}
Future<void> pauseMusic() async => _methodChannel.invokeMethod('pause');
Future<void> resumeMusic() async => _methodChannel.invokeMethod('resume');
Future<void> nextTrack() async => _methodChannel.invokeMethod('next');
Future<void> previousTrack() async => _methodChannel.invokeMethod('previous');
Future<void> stopMusic() async => _methodChannel.invokeMethod('stop');
Future<void> setVolume(double volume) async => _methodChannel.invokeMethod(
'setVolume',
{'volume': volume},
); // volume: 0.0~1.0
Future<Map<String, dynamic>> getStatus() async {
final result = await _methodChannel.invokeMethod<Map>('getStatus');
return {
'isPlaying': result?['isPlaying'] ?? false,
'id': result?['id'] ?? 0,
};
}
/// 获取播放进度信息
Future<Map<String, dynamic>> getProgress() async {
final result = await _methodChannel.invokeMethod<Map>('getProgress');
return {
'currentPosition': result?['currentPosition'] ?? 0, // 当前播放位置(毫秒)
'duration': result?['duration'] ?? 0, // 总时长(毫秒)
'progress': result?['progress'] ?? 0.0, // 播放进度(0.0-1.0)
};
}
/// 跳转到指定位置
Future<void> seekTo(int positionMs) async {
await _methodChannel.invokeMethod('seekTo', {'position': positionMs});
}
/// 播放音乐列表
Future<void> playMusicPlaylist(List<Map<String, dynamic>> songs) async {
await _methodChannel.invokeMethod('playlist', {'songs': songs});
}
/// 更新媒体会话进度信息
Future<void> updateMediaSessionProgress(
int currentPosition,
int duration,
double progress,
) async {
await _methodChannel.invokeMethod('updateMediaSessionProgress', {
'currentPosition': currentPosition,
'duration': duration,
'progress': progress,
});
}
/// 设置媒体会话回调
Future<void> setMediaSessionCallbacks({
required Function() onPlay,
required Function() onPause,
required Function() onNext,
required Function() onPrevious,
required Function(int) onSeekTo,
required Function() onStop,
required Function(int, int, int, double) onProgress,
}) async {
// 设置方法调用处理器来处理来自原生平台的回调
_methodChannel.setMethodCallHandler((call) async {
switch (call.method) {
case 'onMediaSessionPlay':
onPlay();
break;
case 'onMediaSessionPause':
onPause();
break;
case 'onMediaSessionNext':
onNext();
break;
case 'onMediaSessionPrevious':
onPrevious();
break;
case 'onMediaSessionSeekTo':
final position = call.arguments['position'] as int;
onSeekTo(position);
break;
case 'onMediaSessionStop':
onStop();
break;
case 'onMediaSessionProgress':
final currentIdInternal = call.arguments['currentIdInternal'] as int;
final currentPosition = call.arguments['currentPosition'] as int;
final duration = call.arguments['duration'] as int;
final progress = call.arguments['progress'] as double;
onProgress(currentIdInternal, currentPosition, duration, progress);
break;
}
});
// 通知原生平台启用媒体会话回调
await _methodChannel.invokeMethod('enableMediaSessionCallbacks');
}
/// 更新媒体会话元数据
Future<void> updateMediaSessionMetadata(Map<String, dynamic> song) async {
await _methodChannel.invokeMethod('updateMediaSessionMetadata', {
'song': song,
});
}
/// 更新媒体会话播放状态
Future<void> updateMediaSessionPlaybackState(bool isPlaying) async {
await _methodChannel.invokeMethod('updateMediaSessionPlaybackState', {
'isPlaying': isPlaying,
});
}
}
export 'music_service_native.dart'
if (dart.library.html) 'music_service_stub.dart';

157
local_plugins/music_service/lib/music_service_native.dart

@ -0,0 +1,157 @@
import 'dart:ffi';
import 'package:flutter/services.dart';
class MusicService {
static final MusicService _instance = MusicService._internal();
/// 获取单例
factory MusicService() => _instance;
// 方法通道
final MethodChannel _methodChannel = const MethodChannel(
'com.yunqiinnovation.music_service',
);
// 事件通道 - 简化为只使用数据和状态通道
late final EventChannel _dataEventChannel;
late final EventChannel _statusEventChannel;
// 事件流
late final Stream<dynamic> _dataStream;
late final Stream<dynamic> _statusStream;
// 获取事件流
Stream<dynamic> get onDataReceived => _dataStream;
// 状态流直接传递原始数据
Stream<dynamic> get onStatusUpdated => _statusStream;
// 内部构造函数
MusicService._internal() {
_dataEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/data_events',
);
_statusEventChannel = const EventChannel(
'com.yunqiinnovation.music_service/status_events',
);
// 初始化事件流
_dataStream = _dataEventChannel.receiveBroadcastStream();
_statusStream = _statusEventChannel.receiveBroadcastStream();
}
Future<void> playMusic(Map<String, dynamic> song) async {
await _methodChannel.invokeMethod('play', {'song': song});
}
Future<void> pauseMusic() async => _methodChannel.invokeMethod('pause');
Future<void> resumeMusic() async => _methodChannel.invokeMethod('resume');
Future<void> nextTrack() async => _methodChannel.invokeMethod('next');
Future<void> previousTrack() async => _methodChannel.invokeMethod('previous');
Future<void> stopMusic() async => _methodChannel.invokeMethod('stop');
Future<void> setVolume(double volume) async => _methodChannel.invokeMethod(
'setVolume',
{'volume': volume},
); // volume: 0.0~1.0
Future<Map<String, dynamic>> getStatus() async {
final result = await _methodChannel.invokeMethod<Map>('getStatus');
return {
'isPlaying': result?['isPlaying'] ?? false,
'id': result?['id'] ?? 0,
};
}
/// 获取播放进度信息
Future<Map<String, dynamic>> getProgress() async {
final result = await _methodChannel.invokeMethod<Map>('getProgress');
return {
'currentPosition': result?['currentPosition'] ?? 0, // 当前播放位置(毫秒)
'duration': result?['duration'] ?? 0, // 总时长(毫秒)
'progress': result?['progress'] ?? 0.0, // 播放进度(0.0-1.0)
};
}
/// 跳转到指定位置
Future<void> seekTo(int positionMs) async {
await _methodChannel.invokeMethod('seekTo', {'position': positionMs});
}
/// 播放音乐列表
Future<void> playMusicPlaylist(List<Map<String, dynamic>> songs) async {
await _methodChannel.invokeMethod('playlist', {'songs': songs});
}
/// 更新媒体会话进度信息
Future<void> updateMediaSessionProgress(
int currentPosition,
int duration,
double progress,
) async {
await _methodChannel.invokeMethod('updateMediaSessionProgress', {
'currentPosition': currentPosition,
'duration': duration,
'progress': progress,
});
}
/// 设置媒体会话回调
Future<void> setMediaSessionCallbacks({
required Function() onPlay,
required Function() onPause,
required Function() onNext,
required Function() onPrevious,
required Function(int) onSeekTo,
required Function() onStop,
required Function(int, int, int, double) onProgress,
}) async {
// 设置方法调用处理器来处理来自原生平台的回调
_methodChannel.setMethodCallHandler((call) async {
switch (call.method) {
case 'onMediaSessionPlay':
onPlay();
break;
case 'onMediaSessionPause':
onPause();
break;
case 'onMediaSessionNext':
onNext();
break;
case 'onMediaSessionPrevious':
onPrevious();
break;
case 'onMediaSessionSeekTo':
final position = call.arguments['position'] as int;
onSeekTo(position);
break;
case 'onMediaSessionStop':
onStop();
break;
case 'onMediaSessionProgress':
final currentIdInternal = call.arguments['currentIdInternal'] as int;
final currentPosition = call.arguments['currentPosition'] as int;
final duration = call.arguments['duration'] as int;
final progress = call.arguments['progress'] as double;
onProgress(currentIdInternal, currentPosition, duration, progress);
break;
}
});
// 通知原生平台启用媒体会话回调
await _methodChannel.invokeMethod('enableMediaSessionCallbacks');
}
/// 更新媒体会话元数据
Future<void> updateMediaSessionMetadata(Map<String, dynamic> song) async {
await _methodChannel.invokeMethod('updateMediaSessionMetadata', {
'song': song,
});
}
/// 更新媒体会话播放状态
Future<void> updateMediaSessionPlaybackState(bool isPlaying) async {
await _methodChannel.invokeMethod('updateMediaSessionPlaybackState', {
'isPlaying': isPlaying,
});
}
}

38
local_plugins/music_service/lib/music_service_stub.dart

@ -0,0 +1,38 @@
class MusicService {
static final MusicService _instance = MusicService._internal();
factory MusicService() => _instance;
MusicService._internal();
Future<void> playMusic(Map<String, dynamic> song) async {}
Future<void> playMusicPlaylist(List<Map<String, dynamic>> playlist) async {}
Future<void> pauseMusic() async {}
Future<void> resumeMusic() async {}
Future<void> stopMusic() async {}
Future<void> setVolume(double volume) async {}
Future<Map<String, dynamic>> getStatus() async => {};
Future<Map<String, dynamic>> getProgress() async => {
'isPlaying': false,
'currentPosition': 0,
'duration': 0,
'progress': 0.0,
};
Future<void> seekTo(int positionMs) async {}
Future<void> updateMediaSessionProgress(
int currentPosition, int duration, double progress) async {}
Future<void> setMediaSessionCallbacks({
required Function() onPlay,
required Function() onPause,
required Function() onNext,
required Function() onPrevious,
required Function(int) onSeekTo,
required Function() onStop,
required Function(int, int, int, double) onProgress,
}) async {}
Future<void> updateMediaSessionMetadata(Map<String, dynamic> song) async {}
Future<void> updateMediaSessionPlaybackState(bool isPlaying) async {}
}
Loading…
Cancel
Save