礼物gift以及谷歌service

This commit is contained in:
NIGGER SLAYER 2026-04-15 21:22:05 +08:00
parent 553c37c743
commit 73f1cf1199
8 changed files with 1534 additions and 1410 deletions

View File

@ -1,36 +1,20 @@
{ {
"project_info": { "project_info": {
"project_number": "980005024266", "project_number": "991697445884",
"project_id": "yumi-c3b30", "project_id": "yumi-chat-party",
"storage_bucket": "yumi-c3b30.firebasestorage.app" "storage_bucket": "yumi-chat-party.firebasestorage.app"
}, },
"client": [ "client": [
{ {
"client_info": { "client_info": {
"mobilesdk_app_id": "1:980005024266:android:581aa38059aa318d9c65f3", "mobilesdk_app_id": "1:991697445884:android:1d6eff055324e2ba128879",
"android_client_info": { "android_client_info": {
"package_name": "com.org.yumiparty" "package_name": "com.org.yumiparty"
} }
}, },
"oauth_client": [ "oauth_client": [
{ {
"client_id": "980005024266-o9pjdmdbqqt1julbh1q1ovafcvmr1mv1.apps.googleusercontent.com", "client_id": "991697445884-6b6losu2td9u0era7qui4aeavm16lj57.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_type": 1, "client_type": 1,
"android_info": { "android_info": {
"package_name": "com.org.yumiparty", "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 "client_type": 3
} }
], ],
"api_key": [ "api_key": [
{ {
"current_key": "AIzaSyAbxU9QpbnC5PrrhUjDSK1camiotDlF3qE" "current_key": "AIzaSyDLY8LRCaKYapwXt2BvxUoHYD8kdC1fL70"
} }
], ],
"services": { "services": {
"appinvite_service": { "appinvite_service": {
"other_platform_oauth_client": [ "other_platform_oauth_client": [
{ {
"client_id": "980005024266-dgtthe3q98k8tk873rfdrsnu5ot61p09.apps.googleusercontent.com", "client_id": "991697445884-3ac3fk104691brjgkcejulijnivgr7o2.apps.googleusercontent.com",
"client_type": 3 "client_type": 3
} }
] ]

View File

@ -10,7 +10,6 @@ import 'package:provider/provider.dart';
/// ///
/// ///
class BaseBusinessLogicStrategy implements BusinessLogicStrategy { class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
@override @override
List<Widget> getHomeTabPages(BuildContext context) { List<Widget> getHomeTabPages(BuildContext context) {
final List<Widget> pages = []; final List<Widget> pages = [];
@ -25,7 +24,6 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
return tabs; return tabs;
} }
@override @override
int getHomeInitialTabIndex() { int getHomeInitialTabIndex() {
return 0; return 0;
@ -33,7 +31,10 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
@override @override
void onAvatarTap(BuildContext context) { void onAvatarTap(BuildContext context) {
Provider.of<SCAppGeneralManager>(context, listen: false).openDrawer(context); Provider.of<SCAppGeneralManager>(
context,
listen: false,
).openDrawer(context);
} }
@override @override
@ -125,11 +126,6 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
/// === === /// === ===
@override @override
bool shouldShowPasswordRoomIcon() { bool shouldShowPasswordRoomIcon() {
// //
@ -276,10 +272,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
const Color(0xFF3F0810), const Color(0xFF3F0810),
]; ];
} else { } else {
return [ return [const Color(0xFF666666), const Color(0xFF000000)];
const Color(0xFF666666),
const Color(0xFF000000),
];
} }
} }
@ -287,15 +280,9 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
List<Color> getMePageTabIndicatorGradient(bool isFemale) { List<Color> getMePageTabIndicatorGradient(bool isFemale) {
// Tab指示器渐变 // Tab指示器渐变
if (isFemale) { if (isFemale) {
return [ return [const Color(0xffC62548), const Color(0xff9C0322)];
const Color(0xffC62548),
const Color(0xff9C0322),
];
} else { } else {
return [ return [const Color(0xff141414), const Color(0xffE4E4E4)];
const Color(0xff141414),
const Color(0xffE4E4E4),
];
} }
} }
@ -446,10 +433,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
@override @override
List<Color> getVipPageButtonGradient() { List<Color> getVipPageButtonGradient() {
// VIP按钮渐变颜色 // VIP按钮渐变颜色
return [ return [const Color(0xffFF5722), const Color(0xffFEB219)];
const Color(0xffFF5722),
const Color(0xffFEB219),
];
} }
@override @override
@ -519,7 +503,11 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
} }
@override @override
String getVipPagePreviewImage(int vipLevel, String previewType, String featureName) { String getVipPagePreviewImage(
int vipLevel,
String previewType,
String featureName,
) {
// VIP页面预览图像路径模式 // VIP页面预览图像路径模式
if (featureName == 'profile_frame') { if (featureName == 'profile_frame') {
return "sc_images/vip/sc_icon_vip${vipLevel}_profile_rev.png"; return "sc_images/vip/sc_icon_vip${vipLevel}_profile_rev.png";
@ -666,10 +654,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
@override @override
List<Color> getAdminEditingButtonGradient(String buttonType) { List<Color> getAdminEditingButtonGradient(String buttonType) {
// 使 // 使
return [ return [const Color(0xffC670FF), const Color(0xff7726FF)];
const Color(0xffC670FF),
const Color(0xff7726FF),
];
} }
@override @override
@ -699,10 +684,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
return BoxDecoration( return BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(color: const Color(0xffE6E6E6), width: 1.0),
color: const Color(0xffE6E6E6),
width: 1.0,
),
); );
} }
@ -722,10 +704,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
Map<String, int> getAdminEditingViolationTypeMapping(String targetType) { Map<String, int> getAdminEditingViolationTypeMapping(String targetType) {
// ID映射 // ID映射
if (targetType == 'User') { if (targetType == 'User') {
return { return {'Pornography': 1, 'Illegal information': 2};
'Pornography': 1,
'Illegal information': 2,
};
} else if (targetType == 'Room') { } else if (targetType == 'Room') {
return { return {
'Pornography': 3, 'Pornography': 3,
@ -748,10 +727,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
@override @override
List<Color> getAdminSearchButtonGradient(String pageType) { List<Color> getAdminSearchButtonGradient(String pageType) {
// - // -
return [ return [Colors.transparent, Colors.transparent];
Colors.transparent,
Colors.transparent,
];
} }
@override @override
@ -888,10 +864,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
@override @override
List<Color> getEditProfileGenderButtonGradient(bool isMale, bool isSelected) { List<Color> getEditProfileGenderButtonGradient(bool isMale, bool isSelected) {
// //
return [ return [const Color(0xffFF9326), const Color(0xffFEB219)];
const Color(0xffFF9326),
const Color(0xffFEB219),
];
} }
@override @override
@ -1203,11 +1176,14 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
case 'ACTIVITY': case 'ACTIVITY':
return "sc_images/room/sc_icon_activity_gift_head_bg.png"; return "sc_images/room/sc_icon_activity_gift_head_bg.png";
case 'LUCK': 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': 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': 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: default:
return "sc_images/room/sc_icon_activity_gift_head_bg.png"; return "sc_images/room/sc_icon_activity_gift_head_bg.png";
} }
@ -1520,10 +1496,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
// Tab标签选中文本样式 // Tab标签选中文本样式
// 15"MyCustomFont" // 15"MyCustomFont"
// ScreenUtil().sp // ScreenUtil().sp
return const TextStyle( return const TextStyle(fontSize: 15, fontFamily: "MyCustomFont");
fontSize: 15,
fontFamily: "MyCustomFont",
);
} }
@override @override
@ -1531,10 +1504,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
// Tab标签未选中文本样式 // Tab标签未选中文本样式
// 13"MyCustomFont" // 13"MyCustomFont"
// ScreenUtil().sp // ScreenUtil().sp
return const TextStyle( return const TextStyle(fontSize: 13, fontFamily: "MyCustomFont");
fontSize: 13,
fontFamily: "MyCustomFont",
);
} }
@override @override
@ -1955,20 +1925,14 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
TextStyle getDynamicPageTabLabelStyle() { TextStyle getDynamicPageTabLabelStyle() {
// Tab标签选中文本样式 - TextStyle(fontWeight: FontWeight.bold, fontSize: 16.sp) // Tab标签选中文本样式 - TextStyle(fontWeight: FontWeight.bold, fontSize: 16.sp)
// 使.sp单位使ScreenUtil() // 使.sp单位使ScreenUtil()
return const TextStyle( return const TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
fontWeight: FontWeight.bold,
fontSize: 16,
);
} }
@override @override
TextStyle getDynamicPageTabUnselectedLabelStyle() { TextStyle getDynamicPageTabUnselectedLabelStyle() {
// Tab标签未选中文本样式 - TextStyle(fontWeight: FontWeight.normal, fontSize: 15.sp) // Tab标签未选中文本样式 - TextStyle(fontWeight: FontWeight.normal, fontSize: 15.sp)
// 使.sp单位使ScreenUtil() // 使.sp单位使ScreenUtil()
return const TextStyle( return const TextStyle(fontWeight: FontWeight.normal, fontSize: 15);
fontWeight: FontWeight.normal,
fontSize: 15,
);
} }
/// === === /// === ===
@ -2568,6 +2532,7 @@ class BaseBusinessLogicStrategy implements BusinessLogicStrategy {
// Scaffold背景颜色 - Colors.transparent // Scaffold背景颜色 - Colors.transparent
return Colors.transparent; return Colors.transparent;
} }
@override @override
Color getGoldRecordPageListBackgroundColor() { Color getGoldRecordPageListBackgroundColor() {
// - // -

View File

@ -11,7 +11,6 @@ import '../../../modules/home/popular/party/sc_home_party_page.dart';
/// ///
/// ///
class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy { class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
/// Tab顺序 /// Tab顺序
@override @override
List<Widget> getHomeTabPages(BuildContext context) { List<Widget> getHomeTabPages(BuildContext context) {
@ -134,11 +133,6 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
/// === === /// === ===
@override @override
bool shouldShowPasswordRoomIcon() { bool shouldShowPasswordRoomIcon() {
// UI // UI
@ -1183,11 +1177,14 @@ class Variant1BusinessLogicStrategy extends BaseBusinessLogicStrategy {
case 'ACTIVITY': case 'ACTIVITY':
return "sc_images/room/sc_icon_activity_gift_head_bg.png"; return "sc_images/room/sc_icon_activity_gift_head_bg.png";
case 'LUCK': 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': 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': 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: default:
return "sc_images/room/sc_icon_activity_gift_head_bg.png"; return "sc_images/room/sc_icon_activity_gift_head_bg.png";
} }

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_debouncer/flutter_debouncer.dart'; import 'package:flutter_debouncer/flutter_debouncer.dart';
import 'package:flutter_screenutil/flutter_screenutil.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/data_sources/models/message/sc_floating_message.dart';
import '../../shared/business_logic/usecases/sc_fixed_width_tabIndicator.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 { class GiftPage extends StatefulWidget {
SocialChatUserProfile? toUser; SocialChatUserProfile? toUser;
@ -48,15 +56,26 @@ class GiftPage extends StatefulWidget {
class _GiftPageState extends State<GiftPage> class _GiftPageState extends State<GiftPage>
with SingleTickerProviderStateMixin { 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; BusinessLogicStrategy get _strategy => SCGlobalConfig.businessLogicStrategy;
// int checkedIndex = 0; // int checkedIndex = 0;
SocialChatGiftRes? checkedGift; SocialChatGiftRes? checkedGift;
final List<Widget> _pages = []; final Map<String, SocialChatGiftRes?> _selectedGiftByTab =
final List<Widget> _tabs = []; <String, SocialChatGiftRes?>{};
RtcProvider? rtcProvider; RtcProvider? rtcProvider;
bool isAll = false; bool isAll = false;
@ -82,10 +101,7 @@ class _GiftPageState extends State<GiftPage>
debugPrint('[GiftFX][Send] $message'); debugPrint('[GiftFX][Send] $message');
} }
void _handleGiftSelected( void _applyGiftSelection(SocialChatGiftRes? gift, {bool notify = true}) {
SocialChatGiftRes? gift, {
required int nextGiftType,
}) {
checkedGift = gift; checkedGift = gift;
if (gift != null && if (gift != null &&
(gift.giftSourceUrl ?? "").isNotEmpty && (gift.giftSourceUrl ?? "").isNotEmpty &&
@ -101,9 +117,15 @@ class _GiftPageState extends State<GiftPage>
} }
number = 1; number = 1;
noShowNumber = false; noShowNumber = false;
setState(() { giftType = _giftTypeFromTab(gift?.giftTab);
giftType = nextGiftType; if (notify) {
}); setState(() {});
}
}
void _handleGiftSelected(String tabType, SocialChatGiftRes? gift) {
_selectedGiftByTab[tabType] = gift;
_applyGiftSelection(gift);
} }
@override @override
@ -114,22 +136,6 @@ class _GiftPageState extends State<GiftPage>
Provider.of<SCAppGeneralManager>(context, listen: false).giftActivityList(); Provider.of<SCAppGeneralManager>(context, listen: false).giftActivityList();
// Provider.of<GeneralProvider>(context, listen: false).giftBackpack(); // Provider.of<GeneralProvider>(context, listen: false).giftBackpack();
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance(); 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) { rtcProvider?.roomWheatMap.forEach((k, v) {
if (v.user != null) { if (v.user != null) {
if (v.user?.id == AccountStorage().getCurrentUser()?.userProfile?.id) { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_tabs.clear();
_tabs.add(Tab(text: SCAppLocalizations.of(context)!.gift));
return Consumer<SCAppGeneralManager>( return Consumer<SCAppGeneralManager>(
builder: (context, ref, child) { 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( return SafeArea(
top: false, top: false,
child: Column( child: Column(
@ -243,7 +432,7 @@ class _GiftPageState extends State<GiftPage>
child: BackdropFilter( child: BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15), filter: ui.ImageFilter.blur(sigmaX: 15, sigmaY: 15),
child: Container( child: Container(
color: Color(0xff09372E).withOpacity(0.5), color: const Color(0xff09372E).withValues(alpha: 0.5),
constraints: BoxConstraints(maxHeight: 430.w), constraints: BoxConstraints(maxHeight: 430.w),
child: Column( child: Column(
children: [ children: [
@ -319,7 +508,7 @@ class _GiftPageState extends State<GiftPage>
children: [ children: [
SizedBox(width: 5.w), SizedBox(width: 5.w),
Expanded( Expanded(
child: Container( child: SizedBox(
height: 28.w, height: 28.w,
child: TabBar( child: TabBar(
tabAlignment: TabAlignment.start, tabAlignment: TabAlignment.start,
@ -341,8 +530,11 @@ class _GiftPageState extends State<GiftPage>
), ),
indicatorColor: Colors.transparent, indicatorColor: Colors.transparent,
dividerColor: Colors.transparent, dividerColor: Colors.transparent,
controller: _tabController, controller: tabController,
tabs: _tabs, tabs:
giftTabs
.map((tab) => Tab(text: tab.label))
.toList(),
), ),
), ),
), ),
@ -352,8 +544,18 @@ class _GiftPageState extends State<GiftPage>
Expanded( Expanded(
child: TabBarView( child: TabBarView(
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
controller: _tabController, controller: tabController,
children: _pages, children:
giftTabs
.map(
(tab) => GiftTabPage(
key: ValueKey(tab.type),
tab.type,
(gift) =>
_handleGiftSelected(tab.type, gift),
),
)
.toList(),
), ),
), ),
Row( Row(

View File

@ -24,7 +24,7 @@ const Duration _kGiftPageSkeletonMaxDuration = Duration(milliseconds: 900);
class GiftTabPage extends StatefulWidget { class GiftTabPage extends StatefulWidget {
final String type; final String type;
final bool isDark; final bool isDark;
final Function(int checkedIndex) checkedCall; final ValueChanged<SocialChatGiftRes?> checkedCall;
const GiftTabPage( const GiftTabPage(
this.type, this.type,
@ -53,13 +53,7 @@ class _GiftTabPageState extends State<GiftTabPage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { checkedIndex = widget.type == "CUSTOMIZED" ? 1 : 0;
if (widget.type == "CUSTOMIZED") {
widget.checkedCall(1);
} else {
widget.checkedCall(0);
}
});
} }
@override @override
@ -522,7 +516,7 @@ class _GiftTabPageState extends State<GiftTabPage>
onTap: () { onTap: () {
setState(() { setState(() {
checkedIndex = ref.giftByTab[widget.type]!.indexOf(gift); checkedIndex = ref.giftByTab[widget.type]!.indexOf(gift);
widget.checkedCall(checkedIndex); widget.checkedCall(gift);
}); });
}, },
); );

View File

@ -109,13 +109,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
onError: (ErrorCodeType err, String msg) { onError: (ErrorCodeType err, String msg) {
print('rtc错误${err}'); print('rtc错误${err}');
}, },
onLocalAudioStateChanged:
(
RtcConnection connection,
LocalAudioStreamState state,
LocalAudioStreamReason reason,
) {},
onAudioRoutingChanged: (routing) {},
onAudioMixingStateChanged: ( onAudioMixingStateChanged: (
AudioMixingStateType state, AudioMixingStateType state,
AudioMixingReasonType reason, AudioMixingReasonType reason,
@ -132,15 +125,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
break; break;
} }
}, },
onRemoteAudioStateChanged: (
RtcConnection connection,
int remoteUid,
RemoteAudioState state,
RemoteAudioStateReason reason,
int elapsed,
) {
// print('用户 $remoteUid 音频状态: $state, 原因: $reason');
},
onJoinChannelSuccess: (RtcConnection connection, int elapsed) { onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
print('rtc 自己加入 ${connection.channelId} ${connection.localUid}'); print('rtc 自己加入 ${connection.channelId} ${connection.localUid}');
}, },
@ -1037,6 +1021,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
context!, context!,
listen: false, listen: false,
).engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster); ).engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
Provider.of<RtcProvider>(
context!,
listen: false,
).engine?.muteLocalAudioStream(false);
} }
} }
} }

View File

@ -1,12 +1,10 @@
import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.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_menu_dialog.dart';
import 'package:yumi/ui_kit/widgets/room/room_msg_input.dart'; import 'package:yumi/ui_kit/widgets/room/room_msg_input.dart';
import 'package:provider/provider.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/ui_kit/components/text/sc_text.dart';
import 'package:yumi/shared/tools/sc_room_utils.dart'; import 'package:yumi/shared/tools/sc_room_utils.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.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'; import '../../components/sc_debounce_widget.dart';
class RoomBottomWidget extends StatefulWidget { class RoomBottomWidget extends StatefulWidget {
const RoomBottomWidget({super.key});
@override @override
_RoomBottomWidgetState createState() => _RoomBottomWidgetState(); _RoomBottomWidgetState createState() => _RoomBottomWidgetState();
} }
@ -39,12 +39,10 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
SCDebounceWidget( SCDebounceWidget(
onTap: () { onTap: () {
if (SCRoomUtils.touristCanMsg(context)) { if (SCRoomUtils.touristCanMsg(context)) {
Navigator.push( Navigator.push(context, PopRoute(child: RoomMsgInput()));
context,
PopRoute(child: RoomMsgInput()),
);
} }
}, child: Image.asset( },
child: Image.asset(
"sc_images/room/icon_room_input_t.png", "sc_images/room/icon_room_input_t.png",
width: 30.w, width: 30.w,
height: 30.w, height: 30.w,
@ -174,36 +172,19 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
AccountStorage().getCurrentUser()?.userProfile?.id && AccountStorage().getCurrentUser()?.userProfile?.id &&
!provider.roomWheatMap[k]!.micMute!) { !provider.roomWheatMap[k]!.micMute!) {
if (!provider.isMic) { if (!provider.isMic) {
Provider.of<RtcProvider>( provider.engine?.adjustRecordingSignalVolume(100);
context, provider.engine?.setClientRole(
listen: false,
).engine?.adjustRecordingSignalVolume(100);
Provider.of<RtcProvider>(
context,
listen: false,
).engine?.setClientRole(
role: ClientRoleType.clientRoleBroadcaster, role: ClientRoleType.clientRoleBroadcaster,
); );
provider.engine?.muteLocalAudioStream(false);
} else { } else {
if (Provider.of<RtcProvider>( if (provider.isMusicPlaying) {
context, provider.engine?.adjustRecordingSignalVolume(0);
listen: false,
).isMusicPlaying) {
Provider.of<RtcProvider>(
context,
listen: false,
).engine?.adjustRecordingSignalVolume(0);
} else { } else {
Provider.of<RtcProvider>( provider.engine?.setClientRole(
context,
listen: false,
).engine?.setClientRole(
role: ClientRoleType.clientRoleAudience, role: ClientRoleType.clientRoleAudience,
); );
Provider.of<RtcProvider>( provider.engine?.muteLocalAudioStream(provider.isMic);
context,
listen: false,
).engine?.muteLocalAudioStream(provider.isMic);
} }
} }
} }

View File

@ -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` 悬浮入口:入口位于聊天区右下侧、距右侧约 `15.w`、位于底部操作栏上方约 `100.w`,当前先使用代码绘制的 `🎮` 占位图标,并已在实现处标注后续替换为正式 UI 图片资源。
- 已新增语言房游戏底部弹窗:点击 `game` 入口后会从底部弹出约三分之一屏高的面板,采用房间页现有半透明磨砂风格,便于后续继续沿用当前视觉体系。 - 已新增语言房游戏底部弹窗:点击 `game` 入口后会从底部弹出约三分之一屏高的面板,采用房间页现有半透明磨砂风格,便于后续继续沿用当前视觉体系。
- 已补齐语言房游戏列表占位结构:弹窗内部先用静态游戏数据驱动,并按“`ListView` 可上下滑动 + 每行 `5` 个图标位”的方式实现,后续只需替换入口图、列表图标和真实接口数据即可继续开发。 - 已补齐语言房游戏列表占位结构:弹窗内部先用静态游戏数据驱动,并按“`ListView` 可上下滑动 + 每行 `5` 个图标位”的方式实现,后续只需替换入口图、列表图标和真实接口数据即可继续开发。