Browse Source

上传google的购买代码测试

weicu
liwei1dao 11 months ago
parent
commit
9da7dabf63
  1. 218
      lib/data/services/pay_service.dart
  2. 149
      lib/modules/goods/controllers/goods_base_controller.dart
  3. 147
      lib/modules/goods/views/goodsai_view.dart
  4. 4
      lib/modules/settings/views/settings_view.dart
  5. 3
      pubspec.yaml

218
lib/data/services/pay_service.dart

@ -3,6 +3,9 @@ import 'dart:convert';
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../data/services/network/api.dart'; import '../../data/services/network/api.dart';
import 'package:tobias/tobias.dart'; import 'package:tobias/tobias.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -22,6 +25,9 @@ class PayService extends GetxService {
StreamSubscription<List<PurchaseDetails>>? _iapSubscription; StreamSubscription<List<PurchaseDetails>>? _iapSubscription;
final RxBool iapAvailable = false.obs; final RxBool iapAvailable = false.obs;
PaymentResultCallback? _iapOnResult; PaymentResultCallback? _iapOnResult;
// 最近一次 IAP 购买上下文,用于主动查询时携带验证参数
PurchaseDetails? _lastIapPurchase;
ProductDetails? _lastIapProduct;
String? get lastOrderId => _lastOrderId; String? get lastOrderId => _lastOrderId;
@ -169,11 +175,35 @@ class PayService extends GetxService {
return false; return false;
} }
try { try {
final purchaseParam = PurchaseParam(productDetails: product); // 记录最近一次用于购买的商品详情,便于后续构建查询参数
final ok = await _inAppPurchase.buyConsumable( _lastIapProduct = product;
purchaseParam: purchaseParam, final isAndroid =
autoConsume: true, !kIsWeb && defaultTargetPlatform == TargetPlatform.android;
); bool ok;
if (isAndroid && product is GooglePlayProductDetails) {
// 通过环境变量决定订阅或一次性商品路径,避免访问不兼容的属性
final isSubscription =
_getEnvInt('APP_IAP_GOOGLE_IS_SUBSCRIPTION', defaultValue: 0) == 1;
if (isSubscription) {
print('[IAP-android] 订阅商品(ENV),使用 buyNonConsumable');
final gpParam = GooglePlayPurchaseParam(productDetails: product);
ok = await _inAppPurchase.buyNonConsumable(purchaseParam: gpParam);
} else {
// 普通一次性/消耗型商品,使用 buyConsumable
final purchaseParam = PurchaseParam(productDetails: product);
ok = await _inAppPurchase.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: true,
);
}
} else {
// iOS 或非订阅商品,按消耗型处理(如需支持 iOS 订阅,可通过 ENV 切换调用 buyNonConsumable)
final purchaseParam = PurchaseParam(productDetails: product);
ok = await _inAppPurchase.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: true,
);
}
if (!ok) { if (!ok) {
onResult?.call(false, '无法发起内购'); onResult?.call(false, '无法发起内购');
} else { } else {
@ -188,11 +218,17 @@ class PayService extends GetxService {
Future<void> _handlePurchaseUpdates(List<PurchaseDetails> detailsList) async { Future<void> _handlePurchaseUpdates(List<PurchaseDetails> detailsList) async {
for (final details in detailsList) { for (final details in detailsList) {
// 记录最近一次 IAP 购买详情
_lastIapPurchase = details;
print(
'[IAP] 更新: status=${details.status} product=${details.productID} purchaseID=${details.purchaseID} error=${details.error}');
switch (details.status) { switch (details.status) {
case PurchaseStatus.pending: case PurchaseStatus.pending:
_iapOnResult?.call(true, '支付处理中…'); _iapOnResult?.call(true, '支付处理中…');
break; break;
case PurchaseStatus.error: case PurchaseStatus.error:
print(
'[IAP] 错误: code=${details.error?.code} message=${details.error?.message}');
_iapOnResult?.call(false, details.error?.message ?? '支付错误'); _iapOnResult?.call(false, details.error?.message ?? '支付错误');
break; break;
case PurchaseStatus.purchased: case PurchaseStatus.purchased:
@ -250,6 +286,66 @@ class PayService extends GetxService {
} }
} }
/// 构建 Apple/Google 主动查询(pay_askorder)所需的验证参数
/// 返回与后端 PayAskOrCancelReq 对应的 Map
Future<Map<String, dynamic>> buildIapAskParams() async {
final params = <String, dynamic>{};
if (_lastOrderId != null && _lastOrderId!.isNotEmpty) {
params['order_id'] = _lastOrderId;
}
final platform = _platformString();
if (platform == 'ios') {
final receiptOrData =
_lastIapPurchase?.verificationData.serverVerificationData ?? '';
final txId = _lastIapPurchase?.purchaseID ?? '';
// 是否按交易ID校验,默认 false,可通过环境变量开启
final useTxIdEnv = _getEnvBool('APP_IAP_USE_TXID', defaultValue: false);
// 沙箱标识,默认在非发布模式下认为是沙箱;支持环境变量覆盖
final sandbox =
_getEnvBool('APP_IAP_SANDBOX', defaultValue: !kReleaseMode);
params['apple_receipt_data'] = useTxIdEnv ? txId : receiptOrData;
params['apple_use_transaction_id'] = useTxIdEnv;
params['apple_sandbox'] = sandbox;
} else if (platform == 'android') {
final packageInfo = await PackageInfo.fromPlatform();
final pkg = packageInfo.packageName;
final token =
_lastIapPurchase?.verificationData.serverVerificationData ?? '';
final productId =
_lastIapPurchase?.productID ?? _lastIapProduct?.id ?? '';
// 订阅标识,默认 false;如需严格区分,可在商品元数据或环境变量中配置
final isSub =
_getEnvBool('APP_IAP_GOOGLE_IS_SUBSCRIPTION', defaultValue: false);
params['google_package_name'] = pkg;
params['google_product_id'] = productId;
params['google_purchase_token'] = token;
params['google_is_subscription'] = isSub;
}
return params;
}
bool _getEnvBool(String key, {required bool defaultValue}) {
try {
final v = dotenv.env[key];
if (v != null && v.isNotEmpty) {
final low = v.toLowerCase();
return low == 'true' || low == '1' || low == 'yes';
}
} catch (_) {}
return defaultValue;
}
int _getEnvInt(String key, {required int defaultValue}) {
try {
final v = dotenv.env[key];
if (v != null && v.isNotEmpty) {
return int.tryParse(v.trim()) ?? defaultValue;
}
} catch (_) {}
return defaultValue;
}
String _platformString() { String _platformString() {
if (kIsWeb) return 'web'; if (kIsWeb) return 'web';
switch (defaultTargetPlatform) { switch (defaultTargetPlatform) {
@ -397,7 +493,7 @@ class PayService extends GetxService {
'product_name': productName, 'product_name': productName,
'product_desc': productDesc ?? '', 'product_desc': productDesc ?? '',
'amount': amount, 'amount': amount,
'payment_method': paymentMethod, 'payment_method': 4,
'client_ip': clientIp ?? '', 'client_ip': clientIp ?? '',
'notify_url': notifyUrl ?? '', 'notify_url': notifyUrl ?? '',
'extra_data': extraData ?? '', 'extra_data': extraData ?? '',
@ -517,7 +613,6 @@ class PayService extends GetxService {
productName: description, productName: description,
productDesc: description, productDesc: description,
amount: amount, amount: amount,
paymentMethod: 3, // 支付宝支付
notifyUrl: 'https://api.ideapsound.com/api/home/pay_alipaynotify', notifyUrl: 'https://api.ideapsound.com/api/home/pay_alipaynotify',
); );
@ -612,7 +707,6 @@ class PayService extends GetxService {
required String productName, required String productName,
String? productDesc, String? productDesc,
required int amount, required int amount,
int paymentMethod = 3, // 2表示支付宝
String? clientIp, String? clientIp,
String? notifyUrl, String? notifyUrl,
String? extraData, String? extraData,
@ -624,7 +718,7 @@ class PayService extends GetxService {
'product_name': productName, 'product_name': productName,
'product_desc': productDesc ?? '', 'product_desc': productDesc ?? '',
'amount': amount, 'amount': amount,
'payment_method': paymentMethod, 'payment_method': 3,
'client_ip': clientIp ?? '', 'client_ip': clientIp ?? '',
'notify_url': notifyUrl ?? '', 'notify_url': notifyUrl ?? '',
'extra_data': extraData ?? '', 'extra_data': extraData ?? '',
@ -679,7 +773,6 @@ class PayService extends GetxService {
required String productName, required String productName,
String? productDesc, String? productDesc,
required int amount, required int amount,
int paymentMethod = 5, // 约定:5 表示 PayPal
String? clientIp, String? clientIp,
String? notifyUrl, String? notifyUrl,
String? extraData, String? extraData,
@ -690,7 +783,7 @@ class PayService extends GetxService {
'product_name': productName, 'product_name': productName,
'product_desc': productDesc ?? '', 'product_desc': productDesc ?? '',
'amount': amount, 'amount': amount,
'payment_method': paymentMethod, 'payment_method': 5,
'client_ip': clientIp ?? '', 'client_ip': clientIp ?? '',
'notify_url': notifyUrl ?? '', 'notify_url': notifyUrl ?? '',
'extra_data': extraData ?? '', 'extra_data': extraData ?? '',
@ -828,7 +921,6 @@ class PayService extends GetxService {
productName: description, productName: description,
productDesc: description, productDesc: description,
amount: amount, amount: amount,
paymentMethod: 5,
notifyUrl: 'https://api.ideapsound.com/api/home/pay_paypalnotify', notifyUrl: 'https://api.ideapsound.com/api/home/pay_paypalnotify',
); );
@ -843,4 +935,106 @@ class PayService extends GetxService {
return false; return false;
} }
} }
/// 创建 Apple 内购订单,仅用于在服务端生成并记录订单号,后续回查使用
/// 返回服务端生成的 `order_id`,若失败则返回 null
Future<String?> createAppleIapOrder({
required String productId,
required String productName,
String? productDesc,
required int amount,
}) async {
try {
final resp = await Api.paycreateorder({
'product_id': productId,
'product_name': productName,
'product_desc': productDesc ?? '',
'amount': amount,
'payment_method': 6,
});
if (resp != null) {
String? orderId;
const candidates = [
'order_id',
'orderId',
'out_trade_no',
'outTradeNo'
];
for (final k in candidates) {
final v = resp[k];
if (v is String && v.isNotEmpty) {
orderId = v;
break;
}
}
if (orderId == null && resp['data'] is Map<String, dynamic>) {
final data = resp['data'] as Map<String, dynamic>;
for (final k in candidates) {
final v = data[k];
if (v is String && v.isNotEmpty) {
orderId = v;
break;
}
}
}
_lastOrderId = orderId;
return orderId;
}
return null;
} catch (e) {
print('创建 Apple IAP 订单异常: $e');
return null;
}
}
/// 创建 Google 内购订单,仅用于在服务端生成并记录订单号,后续回查使用
/// 返回服务端生成的 `order_id`,若失败则返回 null
Future<String?> createGoogleIapOrder({
required String productId,
required String productName,
String? productDesc,
required int amount,
}) async {
try {
final resp = await Api.paycreateorder({
'product_id': productId,
'product_name': productName,
'product_desc': productDesc ?? '',
'amount': amount,
'payment_method': 7,
});
if (resp != null) {
String? orderId;
const candidates = [
'order_id',
'orderId',
'out_trade_no',
'outTradeNo'
];
for (final k in candidates) {
final v = resp[k];
if (v is String && v.isNotEmpty) {
orderId = v;
break;
}
}
if (orderId == null && resp['data'] is Map<String, dynamic>) {
final data = resp['data'] as Map<String, dynamic>;
for (final k in candidates) {
final v = data[k];
if (v is String && v.isNotEmpty) {
orderId = v;
break;
}
}
}
_lastOrderId = orderId;
return orderId;
}
return null;
} catch (e) {
print('创建 Google IAP 订单异常: $e');
return null;
}
}
} }

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

@ -455,55 +455,93 @@ abstract class GoodsBaseController extends GetxController {
/// 指定 Apple 内购(App Store IAP) /// 指定 Apple 内购(App Store IAP)
Future<bool> payAppleIAP(DBGoods item, Future<bool> payAppleIAP(DBGoods item,
{PaymentResultCallback? onResult}) async { {PaymentResultCallback? onResult}) async {
// 使用商品id作为内购的 productId(与后台保持一致) try {
final productId = item.id; // 先创建服务端订单,记录 order_id 以便后续回查
return _payService.processApplePayment( final orderId = await _payService.createAppleIapOrder(
productId: productId, productId: item.id,
onResult: (success, msg) { productName: item.name,
// 透传并根据阶段展示 productDesc: item.desc,
onResult?.call(success, msg); amount: item.price,
if (!success) { );
_showPayResultDialog(false, msg); if (orderId == null || orderId.isEmpty) {
} else { onResult?.call(false, '创建 Apple 订单失败');
// 支付成功或额度待刷新时弹窗,其余阶段用轻提示 return false;
final showDialog = msg.contains('支付成功'); }
if (showDialog) {
_showPayResultDialog(true, msg); // 使用商品id作为内购的 productId(与后台保持一致)
final productId = item.id;
return _payService.processApplePayment(
productId: productId,
onResult: (success, msg) {
// 透传并根据阶段展示
onResult?.call(success, msg);
if (!success) {
_showPayResultDialog(false, msg);
} else { } else {
Get.snackbar('提示', msg, // 支付成功或额度待刷新时弹窗,其余阶段用轻提示
snackPosition: SnackPosition.BOTTOM, final showDialog = msg.contains('支付成功');
margin: const EdgeInsets.all(12)); if (showDialog) {
_showPayResultDialog(true, msg);
// 仅在支付成功阶段,携带校验参数主动查询订单
_askOrderWithIapVerification();
} else {
Get.snackbar('提示', msg,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(12));
}
} }
} },
}, );
); } catch (e) {
onResult?.call(false, 'Apple 内购异常: $e');
return false;
}
} }
/// 指定 Google Play 内购(Play Billing IAP) /// 指定 Google Play 内购(Play Billing IAP)
Future<bool> payGoogleIAP(DBGoods item, Future<bool> payGoogleIAP(DBGoods item,
{PaymentResultCallback? onResult}) async { {PaymentResultCallback? onResult}) async {
// 使用商品id作为内购的 productId(与后台保持一致) try {
final productId = item.id; // 先创建服务端订单,记录 order_id 以便后续回查
// 调试:查询并打印当前商品的内购详情,便于确认 ID final orderId = await _payService.createGoogleIapOrder(
await _payService.debugQueryProducts({productId}); productId: item.id,
return _payService.processGooglePayment( productName: item.name,
productId: productId, productDesc: item.desc,
onResult: (success, msg) { amount: item.price,
onResult?.call(success, msg); );
if (!success) { if (orderId == null || orderId.isEmpty) {
_showPayResultDialog(false, msg); onResult?.call(false, '创建 Google 订单失败');
} else { return false;
final showDialog = msg.contains('支付成功'); }
if (showDialog) {
_showPayResultDialog(true, msg); // 使用商品id作为内购的 productId(与后台保持一致)
final productId = item.id;
// 调试:查询并打印当前商品的内购详情,便于确认 ID
await _payService.debugQueryProducts({productId});
return _payService.processGooglePayment(
productId: productId,
onResult: (success, msg) {
onResult?.call(success, msg);
if (!success) {
_showPayResultDialog(false, msg);
} else { } else {
Get.snackbar('提示', msg, final showDialog = msg.contains('支付成功');
snackPosition: SnackPosition.BOTTOM, if (showDialog) {
margin: const EdgeInsets.all(12)); _showPayResultDialog(true, msg);
// 仅在支付成功阶段,携带校验参数主动查询订单
_askOrderWithIapVerification();
} else {
Get.snackbar('提示', msg,
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(12));
}
} }
} },
}, );
); } catch (e) {
onResult?.call(false, 'Google 内购异常: $e');
return false;
}
} }
/// 调试:打印当前页面所有商品的 Google IAP 查询结果 /// 调试:打印当前页面所有商品的 Google IAP 查询结果
@ -565,6 +603,37 @@ abstract class GoodsBaseController extends GetxController {
_showPayResultDialog(success, message); _showPayResultDialog(success, message);
} }
/// Apple/Google 内购:无法监听平台回调时,主动查询订单并携带验证数据
Future<void> _askOrderWithIapVerification() async {
try {
final params = await _payService.buildIapAskParams();
final resp = await Api.payaskorder(params);
print('[IAP] 主动查询订单 resp: $resp');
final order = resp?['order'] as Map<String, dynamic>?;
final user = resp?['user'] as Map<String, dynamic>?;
if (order != null) {
final orderStatus = order['status'];
if (orderStatus == 1 && user != null) {
User.instance.updateUserInfo(user);
Get.snackbar('提示', '订单已确认,额度已刷新',
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.all(12));
return;
}
}
// 若未确认成功,尝试回拉用户信息确保额度同步
try {
final response = await Api.tokenlogin();
if (response is Map && response['user'] is Map<String, dynamic>) {
User.instance
.updateUserInfo(response['user'] as Map<String, dynamic>);
}
} catch (_) {}
} catch (e) {
print('[IAP] 主动查询订单异常: $e');
}
}
void _showPayResultDialog(bool success, String message) { void _showPayResultDialog(bool success, String message) {
final context = Get.context; final context = Get.context;
final isDark = final isDark =

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

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import '../../../data/models/goods.dart'; import '../../../data/models/goods.dart';
import '../../../data/models/user_Info.dart'; import '../../../data/models/user_Info.dart';
import '../controllers/goodsai_controller.dart'; import '../controllers/goodsai_controller.dart';
@ -228,73 +229,83 @@ class GoodsaiView extends GetView<GoodsaiController> {
), ),
), ),
SizedBox(height: 10.h), SizedBox(height: 10.h),
Wrap( MasonryGridView.count(
spacing: 12.w, crossAxisCount: 2,
runSpacing: 12.h, mainAxisSpacing: 12.h,
alignment: WrapAlignment.start, crossAxisSpacing: 12.w,
crossAxisAlignment: WrapCrossAlignment.start, shrinkWrap: true,
children: [ physics: const NeverScrollableScrollPhysics(),
_buildFeatureCard( itemCount: 6,
isDarkMode, itemBuilder: (context, index) {
icon: Icons.music_note, switch (index) {
title: '听歌与推荐', case 0:
lines: [ return _buildFeatureCard(
'检索曲库、播放控制', isDarkMode,
'智能推荐歌单与相似歌曲', icon: Icons.music_note,
], title: '听歌与推荐',
colorSeed: Colors.purple, lines: [
), '检索曲库、播放控制',
_buildFeatureCard( '智能推荐歌单与相似歌曲',
isDarkMode, ],
icon: Icons.navigation, colorSeed: Colors.purple,
title: '导航与路线', );
lines: [ case 1:
'路线规划与拥堵规避', return _buildFeatureCard(
'语音播报与途径点设置', isDarkMode,
], icon: Icons.navigation,
colorSeed: Colors.orange, title: '导航与路线',
), lines: [
_buildFeatureCard( '路线规划与拥堵规避',
isDarkMode, '语音播报与途径点设置',
icon: Icons.wb_sunny, ],
title: '天气查询', colorSeed: Colors.orange,
lines: [ );
'实时天气与预报', case 2:
'穿衣建议与出行提醒', return _buildFeatureCard(
], isDarkMode,
colorSeed: Colors.blue, icon: Icons.wb_sunny,
), title: '天气查询',
_buildFeatureCard( lines: [
isDarkMode, '实时天气与预报',
icon: Icons.place, '穿衣建议与出行提醒',
title: '附近搜索', ],
lines: [ colorSeed: Colors.blue,
'餐饮/加油/停车等地点检索', );
'评分、营业时间与路线直达', case 3:
], return _buildFeatureCard(
colorSeed: Colors.green, isDarkMode,
), icon: Icons.place,
_buildFeatureCard( title: '附近搜索',
isDarkMode, lines: [
icon: Icons.newspaper, '餐饮/加油/停车等地点检索',
title: '新闻资讯', '评分、营业时间与路线直达',
lines: [ ],
'热点快讯与专题速览', colorSeed: Colors.green,
'长文总结与要点提炼', );
], case 4:
colorSeed: Colors.indigo, return _buildFeatureCard(
), isDarkMode,
_buildFeatureCard( icon: Icons.newspaper,
isDarkMode, title: '新闻资讯',
icon: Icons.call, lines: [
title: '通讯助手', '热点快讯与专题速览',
lines: [ '长文总结与要点提炼',
'帮忙拨打电话与发短信', ],
'模板消息与日程提醒', colorSeed: Colors.indigo,
], );
colorSeed: Colors.teal, default:
), return _buildFeatureCard(
], isDarkMode,
icon: Icons.call,
title: '通讯助手',
lines: [
'帮忙拨打电话与发短信',
'模板消息与日程提醒',
],
colorSeed: Colors.teal,
);
}
},
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
Text( Text(
@ -349,9 +360,7 @@ class GoodsaiView extends GetView<GoodsaiController> {
final bgColor = isDarkMode ? Colors.grey[900] : Colors.white; final bgColor = isDarkMode ? Colors.grey[900] : Colors.white;
final mainColor = isDarkMode ? colorSeed[300]! : colorSeed[600]!; final mainColor = isDarkMode ? colorSeed[300]! : colorSeed[600]!;
final subColor = isDarkMode ? Colors.white70 : Colors.grey[700]; final subColor = isDarkMode ? Colors.white70 : Colors.grey[700];
final width = (Get.width - 16.w * 2 - 12.w) / 2;
return Container( return Container(
width: width,
padding: EdgeInsets.all(12.w), padding: EdgeInsets.all(12.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: bgColor, color: bgColor,

4
lib/modules/settings/views/settings_view.dart

@ -265,7 +265,7 @@ class SettingsView extends GetView<SettingsController> {
onTap: () => Get.toNamed(Routes.goodstrade), onTap: () => Get.toNamed(Routes.goodstrade),
), ),
_buildCategoryItem( _buildCategoryItem(
icon: Icons.people_alt, icon: Icons.file_open,
label: "Meet", label: "Meet",
iconBg: isDarkMode iconBg: isDarkMode
? Colors.pink[900]!.withOpacity(0.3) ? Colors.pink[900]!.withOpacity(0.3)
@ -274,7 +274,7 @@ class SettingsView extends GetView<SettingsController> {
onTap: () => Get.toNamed(Routes.goodsmeet), onTap: () => Get.toNamed(Routes.goodsmeet),
), ),
_buildCategoryItem( _buildCategoryItem(
icon: Icons.auto_awesome, icon: Icons.smart_toy,
label: "AI", label: "AI",
iconBg: isDarkMode iconBg: isDarkMode
? Colors.teal[900]!.withOpacity(0.3) ? Colors.teal[900]!.withOpacity(0.3)

3
pubspec.yaml

@ -1,7 +1,7 @@
name: voitrans name: voitrans
description: "Voitrans - AI Voice Assistant." description: "Voitrans - AI Voice Assistant."
publish_to: "none" publish_to: "none"
version: 1.0.18+53 version: 1.0.18+54
environment: environment:
sdk: ">=3.3.0 <4.0.0" sdk: ">=3.3.0 <4.0.0"
@ -19,6 +19,7 @@ dependencies:
get_storage: ^2.1.1 get_storage: ^2.1.1
http: ^1.3.0 http: ^1.3.0
flutter_screenutil: ^5.9.0 flutter_screenutil: ^5.9.0
flutter_staggered_grid_view: ^0.7.0
path_provider: ^2.1.5 path_provider: ^2.1.5
audio_session: ^0.1.18 audio_session: ^0.1.18
flutter_markdown: ^0.7.7+1 flutter_markdown: ^0.7.7+1

Loading…
Cancel
Save