礼物gift以及谷歌service
This commit is contained in:
parent
553c37c743
commit
73f1cf1199
@ -1,36 +1,20 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "980005024266",
|
||||
"project_id": "yumi-c3b30",
|
||||
"storage_bucket": "yumi-c3b30.firebasestorage.app"
|
||||
"project_number": "991697445884",
|
||||
"project_id": "yumi-chat-party",
|
||||
"storage_bucket": "yumi-chat-party.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:980005024266:android:581aa38059aa318d9c65f3",
|
||||
"mobilesdk_app_id": "1:991697445884:android:1d6eff055324e2ba128879",
|
||||
"android_client_info": {
|
||||
"package_name": "com.org.yumiparty"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "980005024266-o9pjdmdbqqt1julbh1q1ovafcvmr1mv1.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.org.yumiparty",
|
||||
"certificate_hash": "3fe178dcc3294f7420df2754cfaf4646d6a19478"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "980005024266-sl5h466pe90jsjmoi4jcd7bqhmckieec.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.org.yumiparty",
|
||||
"certificate_hash": "9ab21924bfbe2c56555860ebabf23c4099fd7412"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "980005024266-vrrp2us89svbqgc93oe7q4ad7uoh8fl6.apps.googleusercontent.com",
|
||||
"client_id": "991697445884-6b6losu2td9u0era7qui4aeavm16lj57.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.org.yumiparty",
|
||||
@ -38,20 +22,28 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "980005024266-dgtthe3q98k8tk873rfdrsnu5ot61p09.apps.googleusercontent.com",
|
||||
"client_id": "991697445884-jc3r56gntpgdeicjj8j8qhkght67f7og.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.org.yumiparty",
|
||||
"certificate_hash": "02a381c217fa1bd986cb00aefb8476efc5bea8e3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "991697445884-3ac3fk104691brjgkcejulijnivgr7o2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyAbxU9QpbnC5PrrhUjDSK1camiotDlF3qE"
|
||||
"current_key": "AIzaSyDLY8LRCaKYapwXt2BvxUoHYD8kdC1fL70"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "980005024266-dgtthe3q98k8tk873rfdrsnu5ot61p09.apps.googleusercontent.com",
|
||||
"client_id": "991697445884-3ac3fk104691brjgkcejulijnivgr7o2.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
]
|
||||
|
||||
@ -10,7 +10,6 @@ import 'package:provider/provider.dart';
|
||||
/// 基础业务逻辑策略实现
|
||||
/// 提供原始应用的默认业务逻辑
|
||||
class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
|
||||
@override
|
||||
List<Widget> getHomeTabPages(BuildContext context) {
|
||||
final List<Widget> pages = [];
|
||||
@ -25,7 +24,6 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int getHomeInitialTabIndex() {
|
||||
return 0;
|
||||
@ -33,7 +31,10 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
|
||||
@override
|
||||
void onAvatarTap(BuildContext context) {
|
||||
Provider.of<SCAppGeneralManager>(context, listen: false).openDrawer(context);
|
||||
Provider.of<SCAppGeneralManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).openDrawer(context);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -125,11 +126,6 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
|
||||
/// === 家族页面差异化方法实现 ===
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool shouldShowPasswordRoomIcon() {
|
||||
// 原始应用:显示密码房间图标
|
||||
@ -276,10 +272,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
const Color(0xFF3F0810),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
const Color(0xFF666666),
|
||||
const Color(0xFF000000),
|
||||
];
|
||||
return [const Color(0xFF666666), const Color(0xFF000000)];
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,15 +280,9 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
List<Color> getMePageTabIndicatorGradient(bool isFemale) {
|
||||
// 原始应用Tab指示器渐变
|
||||
if (isFemale) {
|
||||
return [
|
||||
const Color(0xffC62548),
|
||||
const Color(0xff9C0322),
|
||||
];
|
||||
return [const Color(0xffC62548), const Color(0xff9C0322)];
|
||||
} else {
|
||||
return [
|
||||
const Color(0xff141414),
|
||||
const Color(0xffE4E4E4),
|
||||
];
|
||||
return [const Color(0xff141414), const Color(0xffE4E4E4)];
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,10 +433,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
@override
|
||||
List<Color> getVipPageButtonGradient() {
|
||||
// 原始应用:VIP按钮渐变颜色
|
||||
return [
|
||||
const Color(0xffFF5722),
|
||||
const Color(0xffFEB219),
|
||||
];
|
||||
return [const Color(0xffFF5722), const Color(0xffFEB219)];
|
||||
}
|
||||
|
||||
@override
|
||||
@ -519,7 +503,11 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
}
|
||||
|
||||
@override
|
||||
String getVipPagePreviewImage(int vipLevel, String previewType, String featureName) {
|
||||
String getVipPagePreviewImage(
|
||||
int vipLevel,
|
||||
String previewType,
|
||||
String featureName,
|
||||
) {
|
||||
// 原始应用:VIP页面预览图像路径模式
|
||||
if (featureName == 'profile_frame') {
|
||||
return "sc_images/vip/sc_icon_vip${vipLevel}_profile_rev.png";
|
||||
@ -666,10 +654,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
@override
|
||||
List<Color> getAdminEditingButtonGradient(String buttonType) {
|
||||
// 原始应用:编辑页面按钮渐变颜色,两个按钮使用相同的渐变
|
||||
return [
|
||||
const Color(0xffC670FF),
|
||||
const Color(0xff7726FF),
|
||||
];
|
||||
return [const Color(0xffC670FF), const Color(0xff7726FF)];
|
||||
}
|
||||
|
||||
@override
|
||||
@ -699,10 +684,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
return BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: const Color(0xffE6E6E6),
|
||||
width: 1.0,
|
||||
),
|
||||
border: Border.all(color: const Color(0xffE6E6E6), width: 1.0),
|
||||
);
|
||||
}
|
||||
|
||||
@ -722,10 +704,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
Map<String, int> getAdminEditingViolationTypeMapping(String targetType) {
|
||||
// 原始应用:违规类型ID映射
|
||||
if (targetType == 'User') {
|
||||
return {
|
||||
'Pornography': 1,
|
||||
'Illegal information': 2,
|
||||
};
|
||||
return {'Pornography': 1, 'Illegal information': 2};
|
||||
} else if (targetType == 'Room') {
|
||||
return {
|
||||
'Pornography': 3,
|
||||
@ -748,10 +727,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
@override
|
||||
List<Color> getAdminSearchButtonGradient(String pageType) {
|
||||
// 原始应用:搜索按钮渐变颜色 - 透明渐变
|
||||
return [
|
||||
Colors.transparent,
|
||||
Colors.transparent,
|
||||
];
|
||||
return [Colors.transparent, Colors.transparent];
|
||||
}
|
||||
|
||||
@override
|
||||
@ -888,10 +864,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
@override
|
||||
List<Color> getEditProfileGenderButtonGradient(bool isMale, bool isSelected) {
|
||||
// 原始应用默认性别按钮渐变颜色
|
||||
return [
|
||||
const Color(0xffFF9326),
|
||||
const Color(0xffFEB219),
|
||||
];
|
||||
return [const Color(0xffFF9326), const Color(0xffFEB219)];
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1203,11 +1176,14 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
case 'ACTIVITY':
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
case 'LUCK':
|
||||
return "sc_images/room/sc_icon_luck_gift_head_bg.png";
|
||||
// Fallback to the activity header until dedicated localized assets land.
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
case 'CP':
|
||||
return "sc_images/room/sc_icon_cp_gift_head_bg.png";
|
||||
// Fallback to the activity header until dedicated localized assets land.
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
case 'MAGIC':
|
||||
return "sc_images/room/sc_icon_magic_gift_head_bg.png";
|
||||
// Fallback to the activity header until dedicated localized assets land.
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
default:
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
}
|
||||
@ -1520,10 +1496,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
// 原始应用:语音房间页面Tab标签选中文本样式
|
||||
// 字体大小15,字体族"MyCustomFont"
|
||||
// 注意:页面需要将字体大小乘以ScreenUtil().sp
|
||||
return const TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: "MyCustomFont",
|
||||
);
|
||||
return const TextStyle(fontSize: 15, fontFamily: "MyCustomFont");
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1531,10 +1504,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
// 原始应用:语音房间页面Tab标签未选中文本样式
|
||||
// 字体大小13,字体族"MyCustomFont"
|
||||
// 注意:页面需要将字体大小乘以ScreenUtil().sp
|
||||
return const TextStyle(
|
||||
fontSize: 13,
|
||||
fontFamily: "MyCustomFont",
|
||||
);
|
||||
return const TextStyle(fontSize: 13, fontFamily: "MyCustomFont");
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1955,20 +1925,14 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
TextStyle getDynamicPageTabLabelStyle() {
|
||||
// 原始应用:动态页面Tab标签选中文本样式 - TextStyle(fontWeight: FontWeight.bold, fontSize: 16.sp)
|
||||
// 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil()
|
||||
return const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
);
|
||||
return const TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
|
||||
}
|
||||
|
||||
@override
|
||||
TextStyle getDynamicPageTabUnselectedLabelStyle() {
|
||||
// 原始应用:动态页面Tab标签未选中文本样式 - TextStyle(fontWeight: FontWeight.normal, fontSize: 15.sp)
|
||||
// 注意:这里无法使用.sp单位,需要在调用处使用ScreenUtil()
|
||||
return const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 15,
|
||||
);
|
||||
return const TextStyle(fontWeight: FontWeight.normal, fontSize: 15);
|
||||
}
|
||||
|
||||
/// === 启动页面差异化方法实现 ===
|
||||
@ -2568,6 +2532,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
|
||||
// 原始应用:账户页面Scaffold背景颜色 - Colors.transparent
|
||||
return Colors.transparent;
|
||||
}
|
||||
|
||||
@override
|
||||
Color getGoldRecordPageListBackgroundColor() {
|
||||
// 马甲包策略:金币记录页面列表背景颜色 - 深灰色
|
||||
|
||||
@ -11,7 +11,6 @@ import '../../../modules/home/popular/party/sc_home_party_page.dart';
|
||||
/// 马甲包业务逻辑策略实现
|
||||
/// 提供与原始应用不同的业务逻辑
|
||||
class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
|
||||
/// 马甲包版本:调整Tab顺序,家族放在最后
|
||||
@override
|
||||
List<Widget> getHomeTabPages(BuildContext context) {
|
||||
@ -134,11 +133,6 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
|
||||
/// === 家族页面差异化方法实现(马甲包专属) ===
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool shouldShowPasswordRoomIcon() {
|
||||
// 马甲包策略:不显示密码房间图标,简化UI
|
||||
@ -1183,11 +1177,14 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
case 'ACTIVITY':
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
case 'LUCK':
|
||||
return "sc_images/room/sc_icon_luck_gift_head_bg.png";
|
||||
// Fallback to the activity header until dedicated localized assets land.
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
case 'CP':
|
||||
return "sc_images/room/sc_icon_cp_gift_head_bg.png";
|
||||
// Fallback to the activity header until dedicated localized assets land.
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
case 'MAGIC':
|
||||
return "sc_images/room/sc_icon_magic_gift_head_bg.png";
|
||||
// Fallback to the activity header until dedicated localized assets land.
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
default:
|
||||
return "sc_images/room/sc_icon_activity_gift_head_bg.png";
|
||||
}
|
||||
@ -1531,7 +1528,7 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
@override
|
||||
Color getRechargePageScaffoldBackgroundColor() {
|
||||
// 马甲包策略:充值页面Scaffold背景颜色 - 深灰色
|
||||
return Colors.transparent; // #1a1a1a
|
||||
return Colors.transparent; // #1a1a1a
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1668,7 +1665,7 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
@override
|
||||
Color getGoldRecordPageScaffoldBackgroundColor() {
|
||||
// 马甲包策略:金币记录页面Scaffold背景颜色 - 深灰色
|
||||
return Colors.transparent; // #1a1a1a
|
||||
return Colors.transparent; // #1a1a1a
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1811,13 +1808,13 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
@override
|
||||
Color getStoreItemUnselectedBorderColor() {
|
||||
// 马甲包策略:Store商品项未选中边框颜色 - 调整为浅灰色
|
||||
return Colors.transparent; // 浅灰色,替代Colors.black12
|
||||
return Colors.transparent; // 浅灰色,替代Colors.black12
|
||||
}
|
||||
|
||||
@override
|
||||
Color getStoreItemSelectedBorderColor() {
|
||||
// 马甲包策略:Store商品项选中边框颜色 - 使用马甲包主题色
|
||||
return SocialChatTheme.primaryLight; // 马甲包主题橙色,替代Color(0xffFF9500)
|
||||
return SocialChatTheme.primaryLight; // 马甲包主题橙色,替代Color(0xffFF9500)
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1829,7 +1826,7 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
@override
|
||||
Color getStoreItemPriceTextColor() {
|
||||
// 马甲包策略:Store商品项价格文本颜色 - 使用马甲包主要文本颜色
|
||||
return SocialChatTheme.primaryLight; // 马甲包主要文本颜色,替代Colors.black
|
||||
return SocialChatTheme.primaryLight; // 马甲包主要文本颜色,替代Colors.black
|
||||
}
|
||||
|
||||
@override
|
||||
@ -2061,7 +2058,7 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
@override
|
||||
Color getSearchPageScaffoldBackgroundColor() {
|
||||
// 马甲包策略:搜索页面Scaffold背景颜色 - 使用浅灰色背景
|
||||
return Colors.transparent; // 马甲包浅灰色背景颜色,替代Colors.white
|
||||
return Colors.transparent; // 马甲包浅灰色背景颜色,替代Colors.white
|
||||
}
|
||||
|
||||
@override
|
||||
@ -2436,7 +2433,7 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
@override
|
||||
Color getSettingsPageContainerBorderColor() {
|
||||
// 马甲包策略:设置页面容器边框颜色 - 使用浅灰色
|
||||
return Colors.transparent;
|
||||
return Colors.transparent;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -2560,6 +2557,6 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
|
||||
@override
|
||||
Color getGoldRecordPageListBackgroundColor() {
|
||||
// 马甲包策略:金币记录页面列表背景颜色 - 深灰色
|
||||
return Colors.transparent; // #1a1a1a
|
||||
return Colors.transparent; // #1a1a1a
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_debouncer/flutter_debouncer.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
@ -37,6 +38,13 @@ import '../../shared/data_sources/models/enum/sc_gift_type.dart';
|
||||
import '../../shared/data_sources/models/message/sc_floating_message.dart';
|
||||
import '../../shared/business_logic/usecases/sc_fixed_width_tabIndicator.dart';
|
||||
|
||||
class _GiftPageTabItem {
|
||||
const _GiftPageTabItem({required this.type, required this.label});
|
||||
|
||||
final String type;
|
||||
final String label;
|
||||
}
|
||||
|
||||
class GiftPage extends StatefulWidget {
|
||||
SocialChatUserProfile? toUser;
|
||||
|
||||
@ -48,15 +56,26 @@ class GiftPage extends StatefulWidget {
|
||||
|
||||
class _GiftPageState extends State<GiftPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
static const List<String> _preferredGiftTabOrder = <String>[
|
||||
"ALL",
|
||||
"ACTIVITY",
|
||||
"LUCKY_GIFT",
|
||||
"CP",
|
||||
"MAGIC",
|
||||
"CUSTOMIZED",
|
||||
"NSCIONAL_FLAG",
|
||||
];
|
||||
|
||||
TabController? _tabController;
|
||||
List<String> _tabTypes = <String>[];
|
||||
|
||||
/// 业务逻辑策略访问器
|
||||
BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
|
||||
|
||||
// int checkedIndex = 0;
|
||||
SocialChatGiftRes? checkedGift;
|
||||
final List<Widget> _pages = [];
|
||||
final List<Widget> _tabs = [];
|
||||
final Map<String, SocialChatGiftRes?> _selectedGiftByTab =
|
||||
<String, SocialChatGiftRes?>{};
|
||||
RtcProvider? rtcProvider;
|
||||
|
||||
bool isAll = false;
|
||||
@ -82,10 +101,7 @@ class _GiftPageState extends State<GiftPage>
|
||||
debugPrint('[GiftFX][Send] $message');
|
||||
}
|
||||
|
||||
void _handleGiftSelected(
|
||||
SocialChatGiftRes? gift, {
|
||||
required int nextGiftType,
|
||||
}) {
|
||||
void _applyGiftSelection(SocialChatGiftRes? gift, {bool notify = true}) {
|
||||
checkedGift = gift;
|
||||
if (gift != null &&
|
||||
(gift.giftSourceUrl ?? "").isNotEmpty &&
|
||||
@ -101,9 +117,15 @@ class _GiftPageState extends State<GiftPage>
|
||||
}
|
||||
number = 1;
|
||||
noShowNumber = false;
|
||||
setState(() {
|
||||
giftType = nextGiftType;
|
||||
});
|
||||
giftType = _giftTypeFromTab(gift?.giftTab);
|
||||
if (notify) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _handleGiftSelected(String tabType, SocialChatGiftRes? gift) {
|
||||
_selectedGiftByTab[tabType] = gift;
|
||||
_applyGiftSelection(gift);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -114,22 +136,6 @@ class _GiftPageState extends State<GiftPage>
|
||||
Provider.of<SCAppGeneralManager>(context, listen: false).giftActivityList();
|
||||
// Provider.of<GeneralProvider>(context, listen: false).giftBackpack();
|
||||
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
|
||||
_pages.add(
|
||||
GiftTabPage("ALL", (int checkedI) {
|
||||
var all =
|
||||
Provider.of<SCAppGeneralManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).giftByTab["ALL"];
|
||||
_handleGiftSelected(
|
||||
all != null ? all[checkedI] : null,
|
||||
nextGiftType: 0,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
_tabController = TabController(length: _pages.length, vsync: this);
|
||||
_tabController.addListener(() {}); // 监听切换
|
||||
rtcProvider?.roomWheatMap.forEach((k, v) {
|
||||
if (v.user != null) {
|
||||
if (v.user?.id == AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||||
@ -147,12 +153,195 @@ class _GiftPageState extends State<GiftPage>
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController?.removeListener(_handleTabChanged);
|
||||
_tabController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleTabChanged() {
|
||||
final controller = _tabController;
|
||||
if (!mounted ||
|
||||
controller == null ||
|
||||
controller.index >= _tabTypes.length) {
|
||||
return;
|
||||
}
|
||||
final ref = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||
_syncSelectedGiftForTab(ref, _tabTypes[controller.index]);
|
||||
}
|
||||
|
||||
List<_GiftPageTabItem> _buildGiftTabs(
|
||||
BuildContext context,
|
||||
SCAppGeneralManager ref,
|
||||
) {
|
||||
final localizations = SCAppLocalizations.of(context)!;
|
||||
final availableTypes =
|
||||
ref.giftByTab.entries
|
||||
.where((entry) => entry.value.isNotEmpty)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
final orderedTypes = <String>[];
|
||||
for (final type in _preferredGiftTabOrder) {
|
||||
if (availableTypes.remove(type)) {
|
||||
orderedTypes.add(type);
|
||||
}
|
||||
}
|
||||
orderedTypes.addAll(availableTypes);
|
||||
if (orderedTypes.isEmpty) {
|
||||
orderedTypes.add("ALL");
|
||||
}
|
||||
return orderedTypes
|
||||
.map(
|
||||
(type) => _GiftPageTabItem(
|
||||
type: type,
|
||||
label: _giftTabLabel(localizations, type),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
void _ensureTabController(
|
||||
SCAppGeneralManager ref,
|
||||
List<_GiftPageTabItem> tabs,
|
||||
) {
|
||||
final nextTypes = tabs.map((tab) => tab.type).toList();
|
||||
if (_tabController != null && listEquals(_tabTypes, nextTypes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentType =
|
||||
(_tabController != null &&
|
||||
_tabTypes.isNotEmpty &&
|
||||
_tabController!.index < _tabTypes.length)
|
||||
? _tabTypes[_tabController!.index]
|
||||
: null;
|
||||
final nextIndex = currentType != null ? nextTypes.indexOf(currentType) : 0;
|
||||
_tabController?.removeListener(_handleTabChanged);
|
||||
_tabController?.dispose();
|
||||
_tabTypes = nextTypes;
|
||||
_tabController = TabController(
|
||||
length: tabs.length,
|
||||
vsync: this,
|
||||
initialIndex: nextIndex >= 0 ? nextIndex : 0,
|
||||
)..addListener(_handleTabChanged);
|
||||
_syncSelectedGiftForTab(
|
||||
ref,
|
||||
_tabTypes[_tabController!.index],
|
||||
notify: false,
|
||||
);
|
||||
}
|
||||
|
||||
void _syncSelectedGiftForTab(
|
||||
SCAppGeneralManager ref,
|
||||
String tabType, {
|
||||
bool notify = true,
|
||||
}) {
|
||||
final gifts = ref.giftByTab[tabType] ?? const <SocialChatGiftRes>[];
|
||||
final gift = _resolveSelectedGift(tabType, gifts);
|
||||
_selectedGiftByTab[tabType] = gift;
|
||||
_applyGiftSelection(gift, notify: notify);
|
||||
}
|
||||
|
||||
void _ensureCurrentTabSelection(SCAppGeneralManager ref, String tabType) {
|
||||
final gifts = ref.giftByTab[tabType] ?? const <SocialChatGiftRes>[];
|
||||
if (_matchesCurrentGift(gifts)) {
|
||||
return;
|
||||
}
|
||||
final gift = _resolveSelectedGift(tabType, gifts);
|
||||
_selectedGiftByTab[tabType] = gift;
|
||||
_applyGiftSelection(gift, notify: false);
|
||||
}
|
||||
|
||||
SocialChatGiftRes? _resolveSelectedGift(
|
||||
String tabType,
|
||||
List<SocialChatGiftRes> gifts,
|
||||
) {
|
||||
if (gifts.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final selectedGift = _selectedGiftByTab[tabType];
|
||||
if (selectedGift?.id != null) {
|
||||
for (final gift in gifts) {
|
||||
if (gift.id == selectedGift!.id) {
|
||||
return gift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tabType == "CUSTOMIZED") {
|
||||
return gifts.length > 1 ? gifts[1] : null;
|
||||
}
|
||||
return gifts.first;
|
||||
}
|
||||
|
||||
bool _matchesCurrentGift(List<SocialChatGiftRes> gifts) {
|
||||
final currentGiftId = checkedGift?.id;
|
||||
if (currentGiftId == null) {
|
||||
return gifts.isEmpty;
|
||||
}
|
||||
return gifts.any((gift) => gift.id == currentGiftId);
|
||||
}
|
||||
|
||||
String _giftTabLabel(SCAppLocalizations localizations, String tabType) {
|
||||
switch (tabType) {
|
||||
case "ALL":
|
||||
return localizations.all;
|
||||
case "ACTIVITY":
|
||||
return localizations.activity;
|
||||
case "LUCK":
|
||||
case "LUCKY_GIFT":
|
||||
return localizations.luck;
|
||||
case "CP":
|
||||
return "CP";
|
||||
case "MAGIC":
|
||||
return localizations.magic;
|
||||
case "CUSTOMIZED":
|
||||
return localizations.customized;
|
||||
case "NSCIONAL_FLAG":
|
||||
return localizations.country;
|
||||
default:
|
||||
return tabType
|
||||
.toLowerCase()
|
||||
.split("_")
|
||||
.map(
|
||||
(word) =>
|
||||
word.isEmpty
|
||||
? word
|
||||
: "${word[0].toUpperCase()}${word.substring(1)}",
|
||||
)
|
||||
.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
int _giftTypeFromTab(String? tabType) {
|
||||
switch (tabType) {
|
||||
case "ACTIVITY":
|
||||
return 1;
|
||||
case "LUCK":
|
||||
case "LUCKY_GIFT":
|
||||
return 2;
|
||||
case "CP":
|
||||
return 3;
|
||||
case "MAGIC":
|
||||
return 5;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_tabs.clear();
|
||||
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.gift));
|
||||
return Consumer<SCAppGeneralManager>(
|
||||
builder: (context, ref, child) {
|
||||
final giftTabs = _buildGiftTabs(context, ref);
|
||||
_ensureTabController(ref, giftTabs);
|
||||
final tabController = _tabController;
|
||||
if (tabController == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
_ensureCurrentTabSelection(ref, giftTabs[tabController.index].type);
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
@ -243,7 +432,7 @@ class _GiftPageState extends State<GiftPage>
|
||||
child: BackdropFilter(
|
||||
filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15),
|
||||
child: Container(
|
||||
color: Color(0xff09372E).withOpacity(0.5),
|
||||
color: const Color(0xff09372E).withValues(alpha: 0.5),
|
||||
constraints: BoxConstraints(maxHeight: 430.w),
|
||||
child: Column(
|
||||
children: [
|
||||
@ -319,7 +508,7 @@ class _GiftPageState extends State<GiftPage>
|
||||
children: [
|
||||
SizedBox(width: 5.w),
|
||||
Expanded(
|
||||
child: Container(
|
||||
child: SizedBox(
|
||||
height: 28.w,
|
||||
child: TabBar(
|
||||
tabAlignment: TabAlignment.start,
|
||||
@ -341,8 +530,11 @@ class _GiftPageState extends State<GiftPage>
|
||||
),
|
||||
indicatorColor: Colors.transparent,
|
||||
dividerColor: Colors.transparent,
|
||||
controller: _tabController,
|
||||
tabs: _tabs,
|
||||
controller: tabController,
|
||||
tabs:
|
||||
giftTabs
|
||||
.map((tab) => Tab(text: tab.label))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -352,8 +544,18 @@ class _GiftPageState extends State<GiftPage>
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: _tabController,
|
||||
children: _pages,
|
||||
controller: tabController,
|
||||
children:
|
||||
giftTabs
|
||||
.map(
|
||||
(tab) => GiftTabPage(
|
||||
key: ValueKey(tab.type),
|
||||
tab.type,
|
||||
(gift) =>
|
||||
_handleGiftSelected(tab.type, gift),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
|
||||
@ -24,7 +24,7 @@ const Duration _kGiftPageSkeletonMaxDuration = Duration(milliseconds: 900);
|
||||
class GiftTabPage extends StatefulWidget {
|
||||
final String type;
|
||||
final bool isDark;
|
||||
final Function(int checkedIndex) checkedCall;
|
||||
final ValueChanged<SocialChatGiftRes?> checkedCall;
|
||||
|
||||
const GiftTabPage(
|
||||
this.type,
|
||||
@ -53,13 +53,7 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (widget.type == "CUSTOMIZED") {
|
||||
widget.checkedCall(1);
|
||||
} else {
|
||||
widget.checkedCall(0);
|
||||
}
|
||||
});
|
||||
checkedIndex = widget.type == "CUSTOMIZED" ? 1 : 0;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -522,7 +516,7 @@ class _GiftTabPageState extends State<GiftTabPage>
|
||||
onTap: () {
|
||||
setState(() {
|
||||
checkedIndex = ref.giftByTab[widget.type]!.indexOf(gift);
|
||||
widget.checkedCall(checkedIndex);
|
||||
widget.checkedCall(gift);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@ -109,13 +109,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
onError: (ErrorCodeType err, String msg) {
|
||||
print('rtc错误${err}');
|
||||
},
|
||||
onLocalAudioStateChanged:
|
||||
(
|
||||
RtcConnection connection,
|
||||
LocalAudioStreamState state,
|
||||
LocalAudioStreamReason reason,
|
||||
) {},
|
||||
onAudioRoutingChanged: (routing) {},
|
||||
onAudioMixingStateChanged: (
|
||||
AudioMixingStateType state,
|
||||
AudioMixingReasonType reason,
|
||||
@ -132,15 +125,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
break;
|
||||
}
|
||||
},
|
||||
onRemoteAudioStateChanged: (
|
||||
RtcConnection connection,
|
||||
int remoteUid,
|
||||
RemoteAudioState state,
|
||||
RemoteAudioStateReason reason,
|
||||
int elapsed,
|
||||
) {
|
||||
// print('用户 $remoteUid 音频状态: $state, 原因: $reason');
|
||||
},
|
||||
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
|
||||
print('rtc 自己加入 ${connection.channelId} ${connection.localUid}');
|
||||
},
|
||||
@ -1037,6 +1021,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
context!,
|
||||
listen: false,
|
||||
).engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||||
Provider.of<RtcProvider>(
|
||||
context!,
|
||||
listen: false,
|
||||
).engine?.muteLocalAudioStream(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_menu_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_msg_input.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:yumi/app_localizations.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
@ -19,6 +17,8 @@ import '../../../services/audio/rtm_manager.dart';
|
||||
import '../../components/sc_debounce_widget.dart';
|
||||
|
||||
class RoomBottomWidget extends StatefulWidget {
|
||||
const RoomBottomWidget({super.key});
|
||||
|
||||
@override
|
||||
_RoomBottomWidgetState createState() => _RoomBottomWidgetState();
|
||||
}
|
||||
@ -39,16 +39,14 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
||||
SCDebounceWidget(
|
||||
onTap: () {
|
||||
if (SCRoomUtils.touristCanMsg(context)) {
|
||||
Navigator.push(
|
||||
context,
|
||||
PopRoute(child: RoomMsgInput()),
|
||||
);
|
||||
Navigator.push(context, PopRoute(child: RoomMsgInput()));
|
||||
}
|
||||
}, child: Image.asset(
|
||||
"sc_images/room/icon_room_input_t.png",
|
||||
width: 30.w,
|
||||
height: 30.w,
|
||||
),
|
||||
},
|
||||
child: Image.asset(
|
||||
"sc_images/room/icon_room_input_t.png",
|
||||
width: 30.w,
|
||||
height: 30.w,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Consumer<RtcProvider>(
|
||||
@ -174,36 +172,19 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id &&
|
||||
!provider.roomWheatMap[k]!.micMute!) {
|
||||
if (!provider.isMic) {
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).engine?.adjustRecordingSignalVolume(100);
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).engine?.setClientRole(
|
||||
provider.engine?.adjustRecordingSignalVolume(100);
|
||||
provider.engine?.setClientRole(
|
||||
role: ClientRoleType.clientRoleBroadcaster,
|
||||
);
|
||||
provider.engine?.muteLocalAudioStream(false);
|
||||
} else {
|
||||
if (Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).isMusicPlaying) {
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).engine?.adjustRecordingSignalVolume(0);
|
||||
if (provider.isMusicPlaying) {
|
||||
provider.engine?.adjustRecordingSignalVolume(0);
|
||||
} else {
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).engine?.setClientRole(
|
||||
provider.engine?.setClientRole(
|
||||
role: ClientRoleType.clientRoleAudience,
|
||||
);
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).engine?.muteLocalAudioStream(provider.isMic);
|
||||
provider.engine?.muteLocalAudioStream(provider.isMic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
需求进度.md
5
需求进度.md
@ -13,6 +13,11 @@
|
||||
- 本轮按需求暂未处理网络链路上的启动等待,例如审核态检查或远端启动页配置请求。
|
||||
|
||||
## 已完成模块
|
||||
- 已排查语言房“双设备、不同账号进入同一房间,上麦后无声”的直接原因:当前 RTC 进房时统一以 Agora `Audience` 身份加入频道,见 `lib/services/audio/rtc_manager.dart` 中 `joinChannel()` 的 `clientRoleType: clientRoleAudience`;而本地麦克风开关 `isMic` 默认值为 `true`(当前语义实际是“闭麦”),上麦 `shangMai()` 后只有在 `!isMic` 时才会切到 `Broadcaster` 并取消本地静音,所以“只上麦、不点底部麦克风按钮”时不会发声。
|
||||
- 已补充语言房当前真实交互结论:现有实现里“上麦”和“开麦”是两个动作。用户点空麦位后只是占麦;还需要再点击底部麦克风按钮,触发 `lib/ui_kit/widgets/room/room_bottom_widget.dart` 中的角色切换,才能真正开始向房间发送音频。
|
||||
- 已同步补充该需求的后续决策点:如果产品预期是“上麦即能说话”,则需要单独改需求并调整实现为“上麦成功后自动切 `Broadcaster` 且自动开麦”;如果继续保留当前双步骤交互,则至少要补一条明确提示文案/引导,并把“双设备不同账号同房,上麦后需手动开麦才能互听”加入验收用例,避免测试误判为 RTC 故障。
|
||||
- 已重新收敛语言房无声修复方案:撤回上一轮会影响进房的较大改动后,当前改为只做最小行为修补,不触碰进房和 token/join 链路;已在“底部麦克风点开”和“麦位解除静音且本人处于开麦态”两条路径上补齐 `muteLocalAudioStream(false)`,修正 UI 显示已开麦但 Agora 本地音频流仍保持静音的问题。
|
||||
- 已按测试收尾要求移除语言房 RTC 诊断面板:该组件仅用于当前无声问题的真机排查,现已从房间页和相关临时诊断代码中整体删除,不作为正式功能保留。
|
||||
- 已为语言房页面补充右下方 `game` 悬浮入口:入口位于聊天区右下侧、距右侧约 `15.w`、位于底部操作栏上方约 `100.w`,当前先使用代码绘制的 `🎮` 占位图标,并已在实现处标注后续替换为正式 UI 图片资源。
|
||||
- 已新增语言房游戏底部弹窗:点击 `game` 入口后会从底部弹出约三分之一屏高的面板,采用房间页现有半透明磨砂风格,便于后续继续沿用当前视觉体系。
|
||||
- 已补齐语言房游戏列表占位结构:弹窗内部先用静态游戏数据驱动,并按“`ListView` 可上下滑动 + 每行 `5` 个图标位”的方式实现,后续只需替换入口图、列表图标和真实接口数据即可继续开发。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user