bug修复
This commit is contained in:
parent
058a4d9c4e
commit
14de02c822
@ -44,6 +44,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
final List<Widget> _pages = [];
|
||||
final Set<int> _builtPageIndexes = <int>{0};
|
||||
final List<BottomNavigationBarItem> _bottomItems = [];
|
||||
final ValueNotifier<int> _taskClaimableCount = ValueNotifier<int>(0);
|
||||
SCAppGeneralManager? generalProvider;
|
||||
Locale? _lastLocale;
|
||||
bool _hasShownEntryDialogs = false;
|
||||
@ -63,6 +64,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
.on<RegisterRewardGrantedEvent>()
|
||||
.listen(_onRegisterRewardGranted);
|
||||
_initializePages();
|
||||
unawaited(_loadTaskClaimableCount());
|
||||
generalProvider = Provider.of<SCAppGeneralManager>(context, listen: false);
|
||||
Provider.of<RtcProvider>(
|
||||
context,
|
||||
@ -103,6 +105,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
routeObserver.unsubscribe(this);
|
||||
OverlayManager().setHomeRootTabsVisible(false);
|
||||
_registerRewardSubscription?.cancel();
|
||||
_taskClaimableCount.dispose();
|
||||
WakelockPlus.disable();
|
||||
super.dispose();
|
||||
}
|
||||
@ -115,6 +118,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
unawaited(_showEntryPopupIfNeeded());
|
||||
});
|
||||
unawaited(_loadTaskClaimableCount());
|
||||
}
|
||||
|
||||
@override
|
||||
@ -141,6 +145,7 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
@override
|
||||
void didPopNext() {
|
||||
_setHomeRootTabsVisible(true);
|
||||
unawaited(_loadTaskClaimableCount());
|
||||
}
|
||||
|
||||
@override
|
||||
@ -256,7 +261,12 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
}
|
||||
|
||||
_pages.add(SCIndexHomePage());
|
||||
_pages.add(const TaskPage(showBackButton: false));
|
||||
_pages.add(
|
||||
TaskPage(
|
||||
showBackButton: false,
|
||||
onTaskStatusChanged: () => unawaited(_loadTaskClaimableCount()),
|
||||
),
|
||||
);
|
||||
_pages.add(SCMessagePage());
|
||||
_pages.add(MePage2());
|
||||
}
|
||||
@ -277,6 +287,9 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
}
|
||||
|
||||
void _switchBottomTab(int index) {
|
||||
if (index == 1) {
|
||||
unawaited(_loadTaskClaimableCount());
|
||||
}
|
||||
if (index == _currentIndex) {
|
||||
return;
|
||||
}
|
||||
@ -311,16 +324,8 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
);
|
||||
_bottomItems.add(
|
||||
BottomNavigationBarItem(
|
||||
icon: _buildBottomTabIcon(
|
||||
active: false,
|
||||
svgaPath: "sc_images/index/sc_icon_explore_anim.svga",
|
||||
fallbackPath: "sc_images/index/sc_icon_explore_no.png",
|
||||
),
|
||||
activeIcon: _buildBottomTabIcon(
|
||||
active: true,
|
||||
svgaPath: "sc_images/index/sc_icon_explore_anim.svga",
|
||||
fallbackPath: "sc_images/index/sc_icon_explore_en.png",
|
||||
),
|
||||
icon: _buildTaskTabIcon(active: false),
|
||||
activeIcon: _buildTaskTabIcon(active: true),
|
||||
label: SCAppLocalizations.of(context)!.task,
|
||||
),
|
||||
);
|
||||
@ -401,6 +406,18 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadTaskClaimableCount() async {
|
||||
try {
|
||||
final count = await SCAccountRepository().taskClaimableCount();
|
||||
if (!mounted) return;
|
||||
_taskClaimableCount.value = count;
|
||||
} catch (error) {
|
||||
debugPrint('[TaskCenter][TabBadge] load failed error=$error');
|
||||
if (!mounted) return;
|
||||
_taskClaimableCount.value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showEntryDialogs() async {
|
||||
if (_hasShownEntryDialogs || !mounted) {
|
||||
return;
|
||||
@ -586,4 +603,34 @@ class _SCIndexPageState extends State<SCIndexPage>
|
||||
fallback: Image.asset(fallbackPath, width: 35.w, height: 35.w),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTaskTabIcon({required bool active}) {
|
||||
return ValueListenableBuilder<int>(
|
||||
valueListenable: _taskClaimableCount,
|
||||
builder: (_, claimableCount, __) {
|
||||
final icon = _buildBottomTabIcon(
|
||||
active: active,
|
||||
svgaPath: "sc_images/index/sc_icon_explore_anim.svga",
|
||||
fallbackPath:
|
||||
active
|
||||
? "sc_images/index/sc_icon_explore_en.png"
|
||||
: "sc_images/index/sc_icon_explore_no.png",
|
||||
);
|
||||
if (claimableCount <= 0) {
|
||||
return icon;
|
||||
}
|
||||
return Badge(
|
||||
backgroundColor: Colors.red,
|
||||
label: text(
|
||||
claimableCount > 99 ? "99+" : "$claimableCount",
|
||||
fontSize: 9.sp,
|
||||
textColor: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
child: icon,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,14 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_user_repository
|
||||
import 'package:yumi/shared/tools/sc_entry_popup_coordinator.dart';
|
||||
|
||||
class TaskPage extends StatefulWidget {
|
||||
const TaskPage({super.key, this.showBackButton = true});
|
||||
const TaskPage({
|
||||
super.key,
|
||||
this.showBackButton = true,
|
||||
this.onTaskStatusChanged,
|
||||
});
|
||||
|
||||
final bool showBackButton;
|
||||
final VoidCallback? onTaskStatusChanged;
|
||||
|
||||
@override
|
||||
State<TaskPage> createState() => _TaskPageState();
|
||||
@ -22,10 +27,7 @@ class TaskPage extends StatefulWidget {
|
||||
|
||||
class _TaskPageState extends State<TaskPage>
|
||||
with WidgetsBindingObserver, RouteAware {
|
||||
static const String _taskMicAsset = 'sc_images/index/sc_icon_task_mic.png';
|
||||
static const String _taskGameAsset = 'sc_images/index/sc_icon_task_game.png';
|
||||
static const String _taskGiftAsset = 'sc_images/index/sc_icon_task_gift.png';
|
||||
static const String _taskDefaultAsset = 'sc_images/index/sc_icon_task.png';
|
||||
static const Duration _taskStatusCacheRefreshDelay = Duration(seconds: 65);
|
||||
|
||||
final SCAccountRepository _repository = SCAccountRepository();
|
||||
@ -108,6 +110,7 @@ class _TaskPageState extends State<TaskPage>
|
||||
_tasks = sortedTasks;
|
||||
_loading = false;
|
||||
});
|
||||
widget.onTaskStatusChanged?.call();
|
||||
} catch (error) {
|
||||
debugPrint('[TaskCenter][Page] load failed error=$error');
|
||||
if (!mounted) return;
|
||||
@ -558,13 +561,7 @@ class _TaskPageState extends State<TaskPage>
|
||||
}
|
||||
|
||||
String _iconForTask(SCTaskListRes task) {
|
||||
final text =
|
||||
'${task.conditionType ?? ''} ${task.taskName ?? ''} ${task.taskDesc ?? ''}'
|
||||
.toLowerCase();
|
||||
if (text.contains('gift')) return _taskGiftAsset;
|
||||
if (text.contains('game')) return _taskGameAsset;
|
||||
if (text.contains('mic')) return _taskMicAsset;
|
||||
return _taskDefaultAsset;
|
||||
return _taskGiftAsset;
|
||||
}
|
||||
|
||||
String _taskProgressText(
|
||||
|
||||
@ -108,10 +108,10 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
static const int _luckyGiftFloatMinMultiple = 5;
|
||||
static const int _luckyGiftBurstMinMultiple = 10;
|
||||
static const int _luckyGiftBurstMinAwardAmount = 5000;
|
||||
static const int _luckyGiftBurstDisplayDurationMs = 2000;
|
||||
static const int _luckyGiftBurstMergeDelayMs = 600;
|
||||
static const int _luckyGiftBurstMergeWindowMs = 1800;
|
||||
static const int _luckyGiftBurstRecentEventTtlMs = 5000;
|
||||
static const int _luckyGiftBurstPlaybackWatchdogMs = 8000;
|
||||
|
||||
BuildContext? context;
|
||||
|
||||
@ -222,6 +222,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
Timer? _luckGiftPushPlayTimer;
|
||||
int _luckGiftPushPlaybackToken = 0;
|
||||
final Map<String, int> _recentLuckGiftPushEventTimes = <String, int>{};
|
||||
int get currentLuckGiftBurstPlaybackToken => _luckGiftPushPlaybackToken;
|
||||
String? roomRedPacketBroadcastGroupId;
|
||||
String? roomRedPacketBroadcastRegionCode;
|
||||
Debouncer debouncer = Debouncer();
|
||||
@ -648,9 +649,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
|
||||
///加入房间的im群聊
|
||||
Future<V2TimCallback> joinRoomGroup(String groupID, String message) async {
|
||||
_luckGiftPushQueue.clear();
|
||||
currentPlayingLuckGift = null;
|
||||
_currentLuckGiftPushKey = null;
|
||||
_resetLuckGiftBurstQueue();
|
||||
var joinResult = await TencentImSDKPlugin.v2TIMManager.joinGroup(
|
||||
groupID: groupID,
|
||||
message: message,
|
||||
@ -2542,6 +2541,75 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendRoomRedPacketBroadcast(
|
||||
Map<String, dynamic> payload, {
|
||||
String roomGroupId = '',
|
||||
}) async {
|
||||
if (payload.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final targetGroupIds = <String>{};
|
||||
final normalizedRoomGroupId = roomGroupId.trim();
|
||||
if (normalizedRoomGroupId.isNotEmpty) {
|
||||
targetGroupIds.add(normalizedRoomGroupId);
|
||||
}
|
||||
|
||||
var regionGroupId = roomRedPacketBroadcastGroupId?.trim() ?? '';
|
||||
if (regionGroupId.isEmpty) {
|
||||
await syncRoomRedPacketBroadcastGroup();
|
||||
regionGroupId = roomRedPacketBroadcastGroupId?.trim() ?? '';
|
||||
}
|
||||
if (regionGroupId.isNotEmpty) {
|
||||
targetGroupIds.add(regionGroupId);
|
||||
}
|
||||
|
||||
if (targetGroupIds.isEmpty) {
|
||||
debugPrint(
|
||||
'[RoomRedPacket][IM] skip send broadcast because target groups are empty',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final message = <String, dynamic>{
|
||||
'type': SCRoomMsgType.roomRedPacket,
|
||||
'data': payload,
|
||||
};
|
||||
final data = jsonEncode(message);
|
||||
for (final groupId in targetGroupIds) {
|
||||
await _sendRoomRedPacketCustomMessage(groupId, data, payload);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _sendRoomRedPacketCustomMessage(
|
||||
String groupId,
|
||||
String data,
|
||||
Map<String, dynamic> payload,
|
||||
) async {
|
||||
try {
|
||||
final textMsg = await TencentImSDKPlugin.v2TIMManager
|
||||
.getMessageManager()
|
||||
.createCustomMessage(data: data);
|
||||
if (textMsg.code != 0 || textMsg.data?.id == null) {
|
||||
debugPrint(
|
||||
'[RoomRedPacket][IM] create message failed '
|
||||
'group=$groupId code=${textMsg.code} desc=${textMsg.desc}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final sendResult = await TencentImSDKPlugin.v2TIMManager
|
||||
.getMessageManager()
|
||||
.sendMessage(id: textMsg.data!.id!, groupID: groupId, receiver: '');
|
||||
debugPrint(
|
||||
'[RoomRedPacket][IM] send message packetId=${payload['packetId']} '
|
||||
'group=$groupId code=${sendResult.code} desc=${sendResult.desc}',
|
||||
);
|
||||
} catch (error) {
|
||||
debugPrint(
|
||||
'[RoomRedPacket][IM] send message failed group=$groupId error=$error',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future quitGroup(String groupID) async {
|
||||
if (groupID.isEmpty) {
|
||||
return;
|
||||
@ -2585,14 +2653,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
roomAllMsgList.clear();
|
||||
roomGiftMsgList.clear();
|
||||
roomChatMsgList.clear();
|
||||
_luckGiftPushQueue.clear();
|
||||
currentPlayingLuckGift = null;
|
||||
_currentLuckGiftPushKey = null;
|
||||
_currentLuckGiftPushEventKeys.clear();
|
||||
_luckGiftPushPlayTimer?.cancel();
|
||||
_luckGiftPushPlayTimer = null;
|
||||
_luckGiftPushPlaybackToken++;
|
||||
_recentLuckGiftPushEventTimes.clear();
|
||||
_resetLuckGiftBurstQueue();
|
||||
onNewMessageListenerGroupMap.forEach((k, v) {
|
||||
v = null;
|
||||
});
|
||||
@ -2654,12 +2715,18 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void cleanLuckGiftBackCoins() {
|
||||
_resetLuckGiftBurstQueue();
|
||||
}
|
||||
|
||||
void _resetLuckGiftBurstQueue() {
|
||||
_luckGiftPushQueue.clear();
|
||||
currentPlayingLuckGift = null;
|
||||
_currentLuckGiftPushKey = null;
|
||||
_currentLuckGiftPushEventKeys.clear();
|
||||
_luckGiftPushPlayTimer?.cancel();
|
||||
_luckGiftPushPlayTimer = null;
|
||||
_luckGiftPushPlaybackToken++;
|
||||
_recentLuckGiftPushEventTimes.clear();
|
||||
}
|
||||
|
||||
void _scheduleLuckGiftBackCoins({bool restart = false}) {
|
||||
@ -2695,20 +2762,25 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
final playbackToken = ++_luckGiftPushPlaybackToken;
|
||||
notifyListeners();
|
||||
Future.delayed(
|
||||
Duration(milliseconds: _luckyGiftBurstDisplayDurationMs),
|
||||
Duration(milliseconds: _luckyGiftBurstPlaybackWatchdogMs),
|
||||
() {
|
||||
if (playbackToken != _luckGiftPushPlaybackToken) {
|
||||
return;
|
||||
}
|
||||
currentPlayingLuckGift = null;
|
||||
_currentLuckGiftPushKey = null;
|
||||
_currentLuckGiftPushEventKeys.clear();
|
||||
notifyListeners();
|
||||
_scheduleLuckGiftBackCoins();
|
||||
completeLuckGiftBackCoins(playbackToken);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void completeLuckGiftBackCoins(int playbackToken) {
|
||||
if (playbackToken != _luckGiftPushPlaybackToken ||
|
||||
currentPlayingLuckGift == null) {
|
||||
return;
|
||||
}
|
||||
currentPlayingLuckGift = null;
|
||||
_currentLuckGiftPushKey = null;
|
||||
_currentLuckGiftPushEventKeys.clear();
|
||||
notifyListeners();
|
||||
_scheduleLuckGiftBackCoins();
|
||||
}
|
||||
|
||||
bool _hasSeenLuckGiftPushEvent(String eventKey, int now) {
|
||||
if (eventKey.isEmpty) {
|
||||
return false;
|
||||
|
||||
@ -262,6 +262,7 @@ class _LuckGiftNomorAnimWidgetState extends State<LuckGiftNomorAnimWidget> {
|
||||
'|${rewardData.multiple ?? 0}'
|
||||
'|${rewardData.normalizedMultipleType}',
|
||||
);
|
||||
final playbackToken = provider.currentLuckGiftBurstPlaybackToken;
|
||||
return SizedBox(
|
||||
height: 380.w,
|
||||
child: Padding(
|
||||
@ -278,6 +279,8 @@ class _LuckGiftNomorAnimWidgetState extends State<LuckGiftNomorAnimWidget> {
|
||||
movieConfigurer:
|
||||
(movieEntity) =>
|
||||
_configureRewardBurstSvga(movieEntity, rewardData),
|
||||
onPlaybackCompleted:
|
||||
() => provider.completeLuckGiftBackCoins(playbackToken),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@ -616,6 +617,12 @@ class _RoomRedPacketSendPanelState extends State<RoomRedPacketSendPanel> {
|
||||
user: currentUser,
|
||||
),
|
||||
);
|
||||
unawaited(
|
||||
rtmProvider.sendRoomRedPacketBroadcast(
|
||||
payload,
|
||||
roomGroupId: roomProfile?.roomAccount ?? '',
|
||||
),
|
||||
);
|
||||
OverlayManager().addMessage(
|
||||
SCFloatingMessage(
|
||||
type: 4,
|
||||
|
||||
1
需求进度.md
1
需求进度.md
@ -558,6 +558,7 @@
|
||||
- 已收口房间外红包领取层触发:红包飘屏 `GO` 只有在目标房间页面当前可见时才直接打开领取层,否则先进房;同语区或房间 IM 收到即时红包时,也只有当前可见房间匹配目标 `roomId` 才自动弹领取层,避免首页、房间列表、我的页直接弹抢红包。
|
||||
- 已按最新反馈修正领取记录头像和跨房进房:领取记录空头像不再回退成红包图标,统一走项目默认头像;红包飘窗 `GO` 跨房点击直接进目标房间,不再弹进入房间二次确认。
|
||||
- 已修正延时红包多入口数据不一致:公屏红包消息会同步写入待领缓存,右侧入口和领取弹层遇到同一 `packetId` 会合并接口列表、缓存和聊天 payload 的倒计时字段,避免飘屏 `GO`/右侧入口丢失倒计时;领取成功后右侧入口会优先按本地已领状态隐藏,避免接口刷新滞后导致残留。
|
||||
- 已补齐红包发送后的 IM 广播:发送成功后除本机兜底展示外,会把 `{type: ROOM_RED_PACKET, data: payload}` 发送到当前房间 IM 群和当前语区 IM 群;同房用户通过房间群看到公屏/飘屏,房外或其它房同语区用户通过语区群看到飘屏,并继续依赖 `packetId` 去重避免重复展示。
|
||||
|
||||
## 已知问题
|
||||
- 已接入语音房表情包素材第一版:桌面 `表情素材` 中的 `fluent_emoji` 与 `monkey` gif 已纳入项目资源,底部聊天入口改为图稿里的短白色胶囊并移除内部输入图标,右侧新增独立表情按钮;同一个房间输入弹层现在可从表情按钮直接打开表情包面板,原 unicode emoji 面板已替换为本地 gif 表情。发送逻辑同步按最新规则分流:用户在麦上发表情时只在自己麦位播放,不进入聊天列表;用户未上麦发表情时进入房间聊天框展示,不触发麦位动画。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user